【问题标题】:Java Thread Serialization , why serialized Thread Object can be startedJava线程序列化,为什么可以启动序列化的线程对象
【发布时间】:2012-05-16 16:09:25
【问题描述】:

java中的线程无法在java中重启,所以我实现了一个java Thread,然后在拿到Thread的序列化对象后尝试重启线程。

import java.io.Serializable;

public class ThreadSerialization extends Thread implements Serializable {

    int iCheck = 10;
    @Override
    public void run() {
        System.out.println("STARTING");
        for(int i=0;i<10;i++){
            iCheck+=i;
        }
    }

}

和序列化算法-

public class CallingThreadSerializable {

    public static void main(String[] args) {
        ThreadSerialization ser = new ThreadSerialization();
        ser.start();
        FileOutputStream fos = null;
        ObjectOutputStream out = null;
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        try {
            fos = new FileOutputStream("thread.ser");
            out = new ObjectOutputStream(fos);
            out.writeObject(ser);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        try {
            fis = new FileInputStream("thread.ser");
            ois = new ObjectInputStream(fis);
            ThreadSerialization ser1 = (ThreadSerialization) ois.readObject();
            System.out.println("---> " + ser1.iCheck);
            ser1.start();
            System.out.println("---> " + ser1.iCheck);
            ois.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

输出-

STARTING
---> 55
---> 55
STARTING

为什么 ser1 对象又开始了?

【问题讨论】:

  • 不再是同一个对象了。

标签: java serialization


【解决方案1】:

有两点:

首先:Thread 不是 Serializable,因此适用于 serializable JavaDoc 的以下摘录:

为了允许序列化不可序列化类的子类型, 亚型可能承担保存和恢复的责任 超类型的公共、受保护和(如果可访问)的状态 包字段。

这意味着,您的班级ThreadSerialization 将有责任存储和恢复Thread 的状态。但是由于Thread 中有许多private 字段,您不能这样做。因此,Thread 中的所有私有字段都默认初始化。现在看看Thread.start() 的实现:

    //...
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    // ...
    start0();
    //...

由于threadStatus 尚未正确存储/恢复,您可以重新开始。

第二:不要混淆实际的操作系统线程和“管理器”对象java.lang.Thread——它们只是松散耦合的。在您的示例中,您只序列化管理器,而不是在 Java 中没有表示的操作系统线程。反序列化后,您有第二个管理器实例,no 附加了 OS 线程。所以告诉经理开始会成功。

【讨论】:

    【解决方案2】:

    如果您反序列化(?)一个对象,那么您实际上是在创建该类的一个新实例,该实例具有与原始对象相同的属性。

    不是同一个对象。

    同样,由于 Thread 本身不实现 Serializable,因此启动性实际上是任何 Serializable 子类中瞬态状态的一部分(中断性也是如此),因为序列化不会恢复从非可序列化超类继承的字段。 extends Thread implements Serializable 的任何类,假设有充分的理由这样做,可能应该有自己的字段来跟踪开始和中断,并在 readObject()readResolve() 中调用 Thread.start()Thread.interrupt() 来恢复这些其状态的元素。

    【讨论】:

    • 不是同一个对象,但是“克隆”的内部状态应该是一样的。此内部状态应防止再次调用 start
    • @A.H.有趣,我给了你 +1
    【解决方案3】:

    在序列化过程中,对象实例变量被吸出,并转换为字节并写入文件(通常是具有 .ser 扩展名的文件),然后,在反序列化过程中,从文件中读取字节,然后转换为实例变量,并用于创建与被序列化的对象相同的新对象。它们的构造函数永远不会被调用,因此它在序列化时而不是在创建时达到状态。

    您可以在序列化之前检查对象的哈希码,然后在反序列化之后,它们将永远不会相同,因为哈希码由 JVM 提供,具体取决于对象在堆上的创建位置,很明显这两个对象不能在同一地址的堆上创建...所以它们是差异对象但具有相同的状态。

    【讨论】:

      【解决方案4】:

      要使对象可序列化,相应的类应显式实现 Serializable 接口。然而,某些由 java 定义的系统类,如“Thread”、“OutputStream”、“Socket”是不可序列化的。为什么这样?让我们退后一步 - 现在使用 System1 内存序列化在 System1 JVM 中运行的线程然后在 System2 中反序列化它并尝试在 System2 JVM 中运行有什么用。说得对!因此这些类是不可序列化的。

      参加你的计划。

      ThreadSerialization ser1 = (ThreadSerialization) ois.readObject();// Thread started in System2.
      ser1.start();// Thread once again started here in System2.
      

      拥有一个实现可序列化并扩展 Thread 或实现 Runnable 的类是非常糟糕的。 总之 不要在密封或流和套接字进程中使用线程。

      【讨论】:

        【解决方案5】:

        不管这个“线程”中的任何其他响应:)

        由于调用了本地方法,线程类无法序列化!!!

        句号!

        编辑:

        如果您在单个进程中工作,您可以声明一个全局变量。 如果您创建多个线程,您可以创建一个包含活动线程列表的类。

        • 此类包含哈希映射。
        • 哈希映射包含作为字符串的键 (id) 和作为对线程的弱引用的值。
        • 线程由 id 标识
        • 您不必担心 GC 如果您的线程完成或自己完成,则类方法返回 null。

        import java.lang.ref.WeakReference;
        import java.util.HashMap;
        
        public class ThreadsMap {
        
            private HashMap<String, WeakReference<Thread>> _threadHashMap;
        
            public ThreadsMap() {
                _threadHashMap = new HashMap<>();
            }
        
            public void add(String id, Thread thread) {
                WeakReference<Thread> threadWeakReference = new WeakReference<>(thread);
                _threadHashMap.put(id, threadWeakReference);
            }
        
            public Thread get(String id) {
                WeakReference<Thread> threadWeakReference = _threadHashMap.get(id);
                return threadWeakReference.get();
            }
        
        }
        

        【讨论】:

          猜你喜欢
          • 2013-05-26
          • 1970-01-01
          • 2011-09-28
          • 1970-01-01
          • 2010-10-21
          • 2011-02-28
          • 1970-01-01
          • 2010-11-21
          相关资源
          最近更新 更多