线程间的协作(线程通信)
线程的状态
Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态)。
线程中各种状态的转换关系如下图:
wait/notify/notifyAll
这三个方法都是Object
上的方法, 只有获取到了所调用对象的monitor
锁才能进行调用。是什么意思呢,举个例子:
1 |
|
上述例子中,只有获取到了lock
对象的monitor
锁(通过synchronize)才能调用lock.wait
(注意,这里是通过lock对象才能调用wait,因为是获取的lock对象的锁)
wait
JDK中一共提供了三种wait方法:
wait()方法的作用是将当前运行的线程挂起(即让其进入阻塞状态),直到notify或notifyAll方法来唤醒线程.
wait(long timeout),该方法与wait()方法类似,唯一的区别就是在指定时间内,如果没有notify或notifAll方法的唤醒,也会自动唤醒
至于wait(long timeout,long nanos),本意在于更精确的控制调度时间
wait方法的使用必须在同步的范围内(获得monitor的锁),否则就会抛出IllegalMonitorStateException
异常,wait方法的作用就是阻塞当前线程等待notify/notifyAll
方法的唤醒,或等待超时后自动唤醒。
notify/notifyAll
既然wait方式是通过对象的monitor对象来实现的,所以只要在同一对象上去调用notify/notifyAll方法,就可以唤醒对应对象monitor上等待的线程了。notify和notifyAll
的区别在于前者只能唤醒monitor上的一个线程,对其他线程没有影响,而n6otifyAll则唤醒所有的线程
sleep/join/yield
这三个方法是Thread
上的方法
sleep
sleep方法的作用是让当前线程暂停指定的时间(毫秒),sleep方法是最简单的方法,在上述的例子中也用到过,比较容易理解。唯一需要注意的是其与wait方法的区别。最简单的区别是,wait方法依赖于同步,而sleep方法可以直接调用。
而更深层次的区别在于sleep方法只是暂时让出CPU的执行权,并不释放锁。而wait方法则会释放锁。通过sleep方法实现的暂停,程序是顺序进入同步块的,只有当上一个线程执行完成的时候,下一个线程才能进入同步方法,sleep暂停期间一直持有monitor对象锁,其他线程是不能进入的.
这个又怎么理解呢,还是用具体的例子来说明:
1 |
|
不管是那个线程先进入sleepSyn
, 都会把threadName sleep start | threadName sleep end
打印完成后,才会让下一个线程访问,也就是说当持有对象锁的时候,sleep
期间是不会释放的
join
join方法的作用是父线程等待子线程执行完成后再执行,换句话说就是将异步执行的线程合并为同步的线程。
join也有三种调用方式:
1 |
|
join的源码如下:
1 |
|
怎么去理解join呢,这个问题最开始困扰了我很久。我的理解是:
发起join调用的线程等待join的线程执行完了之后才会执行
有一些绕口,还是用一个例子来理解:
1 |
|
main thread 会等待 sunThread 执行完了之后在执行。从join的源码可以看到,发起join调用实际上等于获取了sunThread
的monitor锁后,调用了sunThread.wait
,需要等待sunThread.notify(notifyAll)
才能唤醒。但是我们并没有看到唤醒的代码。这是因为当 Thread 退出后(Thread.exit), cpp的源码会自动执行 Thread.notifyAll
。所以就能理解,为什么join线程执行完成后,调用join的线程会被唤醒执行
yield
yield方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止。yield方法只是将Running状态转变为Runnable状态
yield是一种线程让步,暂时让出时间片,但是下一次时间片同样有机会抢占