在并发编程中,保证线程同步,从而实现线程之间正确通讯,是一个值得考虑的问题。本篇将参考许多著名书籍,学习如何让多个线程之间相互配合,完成咱们指定的任务。面试
固然本文只是学习了一部分线程间通讯的方法,还有一些例如使用Lock和Condition对象,管道输入输出、生产者消费者等内容,咱们以后再作学习。编程
synchronized
关键字是Java提供的互斥的内置锁,该锁机制不用显式加锁或者释放锁。互斥执行的特性能够确保对整个临界区代码的执行具备原子性,同步机制保证了共享数据在同一个时刻只被一个线程使用。并发
回顾如下synchronized的底层实现:学习
咱们能够对下面这段代码进行反编译:javap -v TestData.class
。this
public class TestData { public static synchronized void m1(){} public synchronized void m2(){} public static void main(String[] args) { synchronized (TestData.class){ } } }
编译结果以下:
线程
虽然同步方法和代码块的实现细节不一样,可是归根结底:JVM对于方法或者代码块的实现是基于对Monitor对象的进入和退出操做。code
以同步代码块举例:对象
下图源自《Java并发编程得艺术》4-2
blog
新Java内存模型中提供了比锁更加轻量级的通讯机制,它加强了volatile的内存语义,让volatile拥有和锁同样的语义:告知程序任何对volatile修饰变量的访问都要从共享内存中获取,对它的改变必须同步刷新回共享内存,保证了线程对变量访问的可见性。
关于volatile的重点学习,以后再作总结。
等待/通知相关的方法被定义在java.lang.Object
上,这些方法必须由锁对象来调用。同步实例方法为this,静态方法为类对象,代码块的锁是括号里的玩意儿。
这些方法必须须要获取锁对象以后才能调用,也就是必需要在同步块中或同步方法中调用,不然会抛出IllegalMonitorStateException
的异常。
wait()
: 调用该方法的线程进入WAITING
状态,并释放对象的锁,此时当前线程只有被其余线程通知或中断才会返回。
wait(long)
、wait(long, int)
:进入TIMED_WAITING
状态,释放锁,当前线程有通知或中断会返回,时间到了也会返回。
notify()
: 当前线程通知一个在该对象上等待的另外一线程,被唤醒的线程从等待队列(WAITING)被移动到同步队列(BLOCKED)中,意思是被唤醒的线程不会当即执行,须要等当前线程释放锁以后,而且在同步队列中的线程获得了锁才能执行。
notifyAll()
:当前线程通知全部等待在该对象上的线程,将全部在等待队列中的线程所有移到同步队列中。
假设A和B须要获取同一把锁,A进入以后,B进入同步队列,陷入阻塞(BLOCKED)。
若是A中调用锁的wait()方法,A释放锁,并陷入等待(WAITING)。此时另一个线程B获取的当前锁,B运行。
若是此时B中调用锁的notify()方法,A被唤醒,从等待队列转移到同步队列,只有B运行完毕了,锁被释放了,A拿到锁了,A才出来运行。
等待/通知机制依托于同步机制,确保等待线程从wait()方法返回时可以感知到通知线程对变量作出的修改。
sleep()和wait()方法均可以让线程放弃CPU一段时间,进入等待(WAITING)状态。
sleep()静态方法定义在Thread类中,wait()定义在Object类中。
若是线程持有某个对象的监视器,wait()调用以后,当前线程会释放锁,而sleep()则不会释放这个锁。
对于放弃对象监视器,wait()方法和notify()/notifyAll()有必定区别:
锁对象调用wait()方法以后,会当即释放对象监视器。而notify()/notifyAll()则不会当即释放,而是等到线程剩余代码执行完毕以后才会释放监视器。
参考书籍:《Java并发编程的艺术》 方腾飞