昨天偶然看到一段示例代码中用到了线程通信机制,一时间竟然没有看懂
好在最后终于弄清楚了,特此记录一下
首先说明一下基本概念:
synchronize:可以对一个代码块(或方法)进行锁定,同一时间只能有一个线程进入,并执行其中的代码
wait():只能在synchronize代码块中使用,可以使当前线程中止执行,并允许其它线程就进入这段synchronize代码块
notify():唤醒中止执行的线程,使其可以继续执行
为了方便理解,首先看下面这个例子:
public class ThreadTest {
Object obj;
Thread t1;
Thread t2;
public static void main(String [] args) {
ThreadTest threadTest = new ThreadTest();
threadTest.startThread();
}
/** 创建两个子线程,使其并发执行testSynchronized方法 */
public void startThread () {
obj = new Object();
t1 = new Thread() {
public void run() {
testSynchronize("t1");
};
};
t2 = new Thread() {
public void run() {
testSynchronize("t2");
};
};
t1.start();
t2.start();
}
/** 当线程执行到此方法的时候,会首先唤醒之前wait()的线程,然后自己wait() */
public void testSynchronize (String threadName) {
try {
synchronized (obj) {
System.out.println(threadName + " started");
obj.notify();
obj.wait();
System.out.println(threadName + " finished");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
假设线程t1先执行,那么该程序的输出为:
t1 started
t2 started
t1 finished
该程序的运行流程是这样的:
假设是线程t1先执行的话,那么t1会首先进入synchronized代码块,并持有线程锁,此时t2无法进入此代码块
当t1进入synchronized代码块之后,首先打印出"t1 started",然后执行wait()释放线程锁,这样t2就可以进入此代码块了
当t2进入synchronized代码块之后,首先打印出"t2 started",然后调用notify()唤醒t1线程,之后执行wait()释放线程锁,t1就可以继续执行了
当t1打印出"t1 finished"并执行完成之后,由于t2仍然还在等待当中,并且不再有线程能够将其唤醒,所以这个程序便永远无法结束
结合上面的例子,Java中多线程调度的原理如下:
当使用synchronize关键字修饰一段代码的时候,就相当于为这段代码加了一把锁,这个锁叫做线程锁。
当一个线程想要进来的时候,线程会首先尝试去拿这把锁,如果能够拿到这把锁,那么这个线程就会执行synchronize里面的代码。
如果不能拿到这把锁,那么这个线程就会被丢到一个池子里,并不断尝试去拿这把锁,直到能够拿到为止,这个池子叫做线程池。
当拿到这把锁的线程执行到wait()方法时,该线程就会中止执行,并把锁释放出来,这样在这个池子里的其它线程就可以拿到这把锁。
但是调用了wait()方法之后,这个线程并不会回到线程池,而是被放到一个等待队列中,也就是说这个线程就永远不会去尝试获取这把锁了。
直到有其它的线程通过notify()方法将这个线程重新唤醒的时候,这个线程才会被重新放到线程池中,并再次尝试去获取这把锁
还有一个很关键的问题,在上面的例子中,obj这个对象的作用是什么呢?
在Java中,每个对象都单独拥有自己的线程锁、线程池和等待队列
所以,如果要对并发线程进行控制,最重要的就是让多个线程去拿同一把锁,被丢到同一个线程池中,以及在同一个等待队列中等待
所以说,obj这个对象的作用仅仅在于:提供这把锁、这个线程池和这个等待队列
所以说:
synchronized(obj)的含义是:让线程去拿obj的锁,如果拿不到,则被丢到obj的线程池中
obj.wait()的含义是:让线程释放obj的锁,并使其到obj的等待队列中等待
obj.notify()的含义是:唤醒在obj的等待队列中的线程,使其重新回到obj的线程池中
尤其需要注意的是:哪个线程调用obj.wait(),哪个线程等待,而不是obj本身等待
这里的“不是obj本身等待”要如何理解呢?再看下面这个稍微复杂些的例子:
public class ThreadTestPro {
Thread subThread;
public static void main(String[] args) {
ThreadTestPro threadTestPro = new ThreadTestPro();
threadTestPro.startThread();
}
public void startThread() {
subThread = new Thread() {
public void run() {
synchronized (subThread) {
System.out.println("SubThread waiting");
subThread.notify(); // 唤醒在subThread对象的等待队列中的线程
System.out.println("SubThread finished");
}
};
};
subThread.start();
synchronized (subThread) {
try {
System.out.println("MainThread waiting");
subThread.wait(); // 使线程进入subThread对象的等待队列
System.out.println("MainThread finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
该程序的输出为:MainThread waiting
SubThead waiting
SubThead finished
MainThread finished
这个例子乍看上去可能有些费解,其实关键就在第27行的subThead.wait()这行代码上从字面意思上理解,这行代码的含义好像是让subThread这个子线程进行wait(),就好像subThread.sleep()这个方法一样
但记住上面这句话:哪个线程调用obj.wait(),哪个线程等待,而不是obj本身等待
所以这句话的真正含义是:让调用subThread.wait()这个方法的线程等待,而不是让subThread本身等待
由于subThead.wait()这行代码是在主线程中调用的,所以等待的是主线程,而不是subThread这个对象,更不是子线程
该程序的运行流程是这样的:
首先主线程执行,当执行到subThread.start()后,子线程启动(此时只是启动,并非立即开始运行)
主线程进入synchronized(subThread)代码块,并持有subThread对象的锁,此时子线程无法进入synchronized(subThread)代码块
主线程打印语句,然后执行subThread.wait()释放subThread对象的锁,并进入等待队列,注意此时等待的是主线程,而不是subThread!!!
子线程拿到subThread对象的锁,进入synchronized(subThread)代码块开始执行,使主线程从等待队列进入线程池,并打印语句
当子线程执行完毕后,主线程重新获取到subThread对象的锁,并执行完毕
最后,如果实在难以理解的话,不妨把wait和notify转化成下面的写法,这样就容易理解得多:
synchronized(obj):让线程去拿obj对应的锁,如果拿不到,则被丢到obj的线程池中
wait(obj):让线程释放obj对应的锁,并使其到obj的等待队列中等待
notify(obj):唤醒在obj的等待队列中的线程,使其重新回到obj的线程池中