sponsored links

多线程编程 实践篇(三)注:转

[深入了解线程对象与线程,线程与运行环境]

在基础篇中的第一节,我就强调过,要了解多线程编程,首要的两个概念就是线程对象
和线程.

现在我们来深入理解线程对象,线程,运行环境之间的关系,弄清Runnable与Thread的
作用.

在JAVA平台中,序列化机制是一个非常重要的机制,如果不能理解并熟练应用
序列化机制,你就不能称得一个java程序员.

在JAVA平台中,为什么有些对象中可序列化的,而有些对象就不能序列化?

能序列化的对象,简单说是一种可以复制(意味着可以按一定机制进行重构它)
的对象,这种对象说到底就是内存中一些数据的组合.只要按一定位置和顺序组合就能
完整反映这个对象.

而有些对象,是和当前环境相关的,它反映了当前运行的环境和时序,所以不能
被序列,否则在另外的环境和时序中就无法"还原".
    
    比如,一个Socket对象:

Socket sc = new Socket("111.111.111.111",80);
    这个sc对象表示当前正在运行这段代码的主机和IP为"111.111.111.111"的80
端口之间建立的一个物理连结,如果它被序列化,那么在另一个时刻在另一个主机上它如
何能被还原?Socket连结一旦断开,就已经不存在,它不可能在另一个时间被另一个主机
所重现.重现的已经不是原来那个sc对象了.

线程对象也是这种不可序列化对象,当我们new Thread时,已经初始化了当前这
个线程对象所在有主机的运行环境相关的信息,线程调度机制,安全机制等只特定于当前
运行环境的信息,假如它被序列化,在另一个环境中运行的时候原来初始化的运行环境的
信息就不可能在新的环境中运行.而假如要重新初始化,那它已经不是原来那个线程对象
了.

正如Socket封装了两个主机之间的连结,但它们并不是已经连结关传送数据了.
要想传送数据,你还要getInputStream和getOutputStream,并read和write,两台主机之间
才开始真正的"数据连结"

一个Thread对象并建立后,只是有了可以"运行"的令牌,仅仅只是一个"线程对象".
只有当它调用start()后,当前环境才会分配给它一个运行的"空间",让这段代码开始运行.
这个运行的"空间",才叫真正的"线程".也就是说,真正的线程是指当前正在执行的那一个
"事件".是那个线程对象所在的运行环境.

明白了上面的概念,我们再来看看JAVA中为什么要有Runnable对象和Thread对象.

一.从设计技巧上说,JAVA中为了实现回调,无法调用方法指针,那么利用接口来约
束实现者强制提供匹配的方法,并将实现该接口的类的实例作为参数来提供给调用者,这是
JAVA平台实现回调的重要手段.

二.但是从实际的操作来看,对于算法和数据,是不依赖于任何环境的.所以把想要
实现的操作中的算法和数据封装到一个run方法中(由于算法本身是数据的一个部分,所以我
把它们合并称为数据),可以将离数据和环境的逻辑分离开来.使程序员只关心如何实现我
想做的操作,而不要关心它所在的环境.当真正的需要运行的时候再将这段"操作"传给一个
具体当前环境的Thread对象.

三.这是最最重要的原因,[实现数据共享]
    因为一个线程对象不对多次运行.所以把数据放在Thread对象中,不会被多个线程
同时访问.简单说:

class T extends Thread{
        Object x;
        public void run(){//......;}
    }

T t = new T();
    当T的实例t运行后,t所包含的数据x只能被一个t.start();对象共享,除非声明成
    static Object x;
    一个t的实例数据只能被一个线程访问.意思是"一个数据实例对应一个线程".

而假如我们从外部传入数据,比如

class T extends Thread{
        private Object x;
        public T(Object x){
            this.x = x;
        }

public void run(){//......;}
    }
    这样我们就可以先生成一个x对象传给多个Thread对象,多个线程共同操作一个数据.
也就是"一个数据实例对应多个线程".

现在我们把数据更好地组织一下,把要操作的数据Object x和要进行的操作一个封装
到Runnable的run()方法中,把Runnable实例从外部传给多个Thread对象.这样,我们就有了:

[一个对象的多个线程]
    这是以后我们要介绍的线程池的重要概念.