Java/多线程与同步

维基教科书,自由的教学读本
Maggie tim留言 | 贡献2015年12月9日 (三) 17:26的版本 →‎多线程

对于多线程,Java提供了丰富且强健的支持。在源代码方面,Java提供了synchronized关键字,对具体一个对象实现线程独占,完成所谓的原子操作。在程序方面,Java提供的多线程支持主要体现在Object和Thread两个类上,可以看出,Java从开始就视每一个类实例是生存在多线程的环境当中的。

多线程

当你用java命令行开启一个程序的时候,java就运行在一个进程之上了。所谓进程是相对于操作系统来说的,一个程序被操作系统所启动,就是运行了一个进程。但在一个进程内部,因为程序的需要,可以同时运行几个线程。注意,这里的同时的意思是,处理器按时间分配,在不同的时间段运行不同的线程,但具体一个时间点,肯定只能由一个线程存在,而具体的时间分配对于程序是无关的。与进程不同,线程享有共同的堆空间,全局空间,函数等等,但每一个线程拥有自己的栈空间。因为线程享有共同的堆空间,所以会形成在同一时间不同线程对同一个对象的操作。对于那些没有完成的操作,可能由于别的线程的打扰而出现错误,所以必须实现所谓的原子操作,即在同一时间,一个操作只能由一个线程来运行,只有当该线程完成了操作以后,别的线程才可能进入。为了实现原子操作,就必须在线程将要进入该操作的时候申明对操作的控制权。

Java利用synchronized关键字来申明对一个对象的排他占有,该关键字可以用在函数头中,也可以在函数体的一段中声明,具体使用如下

// 使用在函数头中
public class Processor {
    public synchronized operate(){
        // 该函数为原子操作
    }
}

以上的函数说明operate操作为一个原子操作,即每次只能由一个线程进入该函数中运行,而该类的实例为所谓的“控制权”。也就是说,线程在运行该程序的时候始终独占该类的实例,导致别的线程无法进入。接下来的,在函数体中的申明将更好的能看清这一点。

// 使用在函数中
public class Processor {
    public operate(){
        synchronized(obj) {        
            // 该块内为原子操作
        }
    }
}

以上的函数声明了一个程序块为原子操作,而所谓的“控制权”声明在括号里,即obj。也就是说,在线程进入该块前会查看是否有别的线程占有该obj,如果没有,该线程即可进入该块中运行,并且同时拥有该块的控制权,否则,该线程将被挂起,直到在该块中运行的线程退出该块,该线程才能继续进行。当线程退出该块后,线程自动失去对obj的独占权,别的线程才可得以进入。但是,换句话说,在synchronized申明的函数或是程序块中,同时只能存在一个线程。下来补充说明一个特殊情况:

// 使用在函数中
public class Processor {
    public operate(){
        synchronized(obj) {
            obj = new Object();        
            // 该块内不是原子操作
        }
    }
}

在线程进入到synchronized的块中后,obj即被替换,所以现在如有新的线程想进入该程序块时,因为新的obj没有被占用,所以可以进入,该程序块失去了原子操作的功能。

Object类对多线程的支持

与多线程相关的Object成员函数有如下一些:

  1. void notify()
  2. void notifyAll()
  3. void wait()
  4. void wait(long timeout)
  5. void wait(long timeout, int nanos)

其中,如果线程调用wait函数,则该线程则失去对该类实例的控制权,并且将自己挂起,挂起时间由输入参数而定。比如,当一个线程运行在一个原子操作的时候,调用wait函数,就可以不用退出该程序块而让别的线程有机会进入该原子操作。但如果wait的时间到了,却有别的线程还在该程序块中运行当中,则该线程继续挂起,直到占有该程序块的线程退出为止才能继续运行。

Thread类的相关函数