线程间通信使线程成为一个整体,提高系统之间的交互性,在提高CPU利用率的同时可以对线程任务进行有效的把控与监督。
1、volatile 关键字
volatile有两大特性,一是可见性,二是有序性,禁止指令重排序。其中可见性就是可以让线程之间进行通信。
2、等待唤醒(等待通知)机制
(1)wait() 和 notify() 等待唤醒
等待唤醒机制可以基于 wait() 和 notify() 方法来实现,在一个线程内调用该线程锁对象的 wait() 方法, 线程将进入等待队列等待直到被唤醒。
wait()/notify() 方法只能在同步方法或同部块中调用
调用 wait() 方法,线程需要获取该对象的对象级别锁,如果没有持锁,将会抛出IllegalMonitorStateException,执行 wait() 方法后,当前线程立即释放锁
执行 notify() 方法后,当前线程不会立即释放锁,需要等到执行 notify() 方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,此时 wait() 状态所在的线程才可以获取该对象的锁。
notify() 方法可以随机唤醒等待队列中等待同一共享资源的一个(随机的,仅仅一个)线程,并使该线程退出等待队列,进入可运行状态。
notifyAll() 方法可以使所有正在等待队列中等待同一资源的全部线程从等待状态进入可运行状态。
wait() 方法和 sleep() 方法都会响应 interrupt() 方法,抛出中断异常
需要注意的是 sleep() 方法即使抛出中断异常后也不会释放锁
(2)park() 和 unpark() 等待唤醒
LockSupport 是JDK中用来实现线程阻塞和唤醒的工具,线程调用 park() 则等待“许可”,调用 unpark() 则为指定线程提供“许可”。使用它可以在任何场合使线程阻塞,可以指定任何线程进行唤醒,并且不用担心阻塞和唤醒操作的顺序,但要注意连续多次唤醒的效果和一次唤醒是一样的。
3、join() 方法——线程排队
join() 可以理解成线程合并,当在一个线程调用另一个线程的 join() 方法时,当前线程阻塞等待被调用 join() 方法的线程执行完毕才能继续执行,所以 join() 的好处能够保证线程的执行顺序,具有使线程排队的作用,但是如果调用线程的 join() 方法其实已经失去了并行的意义,虽然存在多个线程,但是本质上还是串行的,另外,join() 的实现其实也是基于等待通知机制。
join(long) 和 sleep(long) 的区别
join(long) 方法释放锁,sleep(long) 方法不释放锁。这是因为 join(long) 方法内部是使用 wait(long) 方法来实现的,所以该方法具有释放锁的特点。同样的原因,当 join(long) 在执行过程中遇到 interrupt() 方法时,也会抛出中断异常。// join(long) 基于 wait() 实现
4、管道输入输出流
管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。
管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节, 而后两种面向字符。
5、ThreadLocal类和InheritableThreadLocal类
(1)ThreadLocal类
ThreadLocal类用来存放线程的数据,每个线程都可以通过ThreadLocal来绑定自己的值,ThreadLocal使变量在线程之间具有隔离性,也就是说ThreadLocal存的变量值是私有的。
(2)InheritableThreadLocal类
ThreadLocal 不能实现父子线程间数据的传递(局限),但是,InheritableThreadLocal类可以使子线程能够获取到父线程设置的值。
在InheritableThreadLocal中,如果父线程修改了值,那么子线程将获取到父线程修改后的值。但是子线程修改了值,对父线程的值并无影响。