线程间通信方式
前言
参考项目:https://github.com/wodongx123/ThreadCommunicationDemo
1. 传统线程通信(Synchronized和wait/notify)
使用Object类自带的wait()和notify()/notifyAll()方法进行线程间的通信。
- 对于这种方法,对应的代码段要被synchronized(obj)所包括在内,也就是说,要有同步监视器才能进行通信。
- 调用wait()方法的线程会放弃对象锁,进入堵塞状态。
- 调用notify()会随机选择一个同代码块上的正在wait()的线程唤醒;notifyAll()会选择所有的wait()的线程唤醒。
- 调用notify()/notifyAll()的线程放弃对象锁后(执行完成,或者wait())才会唤醒目标线程,而不是在调用notify()后立刻唤醒。
2. Lock和Condition进行通信
如果使用Lock来进行线程同步的话,就不能再用wait()/notify()机制了,但是Java也提供了一个Condition类让我们可以继续进行线程间的同步机制。
- 使用Lock.newCondition()来实例化一个Condition。
- condition.await()效果基本等同wait()方法,也是放弃对象锁,进入堵塞状态。
- condition.signal()/signalAll()基本等同于notify(),不多说明。
- 对于Condition和Lock机制而言,和Synchronized区别最大的地方是:Synchronized是Java的内置的机能,通过JVM实现;而Lock/Condition则是类,需要通过代码实现。
- 所以,对于Lock/Condition而言,第一重要的是lock之后必须要手动unlock,第二重要的是不同的线程想要使用lock和Condition时,这些线程的lock和condition都必须要是同一对象。
3. 使用volatile关键字
Java支持通过使用volatile关键字的办法来进行线程通信,对于声明了volatile的对象而言,它在所有线程中的数据都会保持相等。
- 声明volatile的对象,每个线程都会相当于从主存获取这个对象的状态。
- 实际上,每个线程都拥有自己的工作内存,声明volatile的对象会在每个线程的工作内存中,拷贝一份同样的数据,以此来达到数据同步。
- 而一旦某个线程修改了自己工作内存的volatile对象的状态,就会立刻通知主存,主存又通知各个线程的工作内存,以此来保证数据的同步。
- volatile无法保证变量的操作是原子性的,开10个线程,每个线程加1000次,大部分无法达到10000的答案。
public class NoAutoMaticTest {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final NoAutoMaticTest test = new NoAutoMaticTest();
//10个线程对加1000次,本应该得到10000,实际上会因为无法保证变量操作是原子性,经常达不到10000
//而如果换成synchronized或者lock都可以得到10000
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
- 要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
- 对变量的写操作不依赖于当前值。
- 该变量没有包含在具有其他变量的不变式中。
4. BlockingQueue
阻塞队列从Java5开始提供,是Queue的字接口,主要通过队列的方式做线程通信。
- 队列是一个FIFO队列,存入元素的位置总是在队列末尾,取出元素的位置总是在队列头部。
- 队列可以供不同的线程取出或者存入元素。
- 当队列已满时,存入元素的线程会根据使用的方法不同而抛出异常、返回false、进入堵塞。
- 当队列元素为空时,取出元素的线程会根据使用的方法不同而抛出异常、返回false、进入堵塞。
抛出异常 | 返回不同值 | 阻塞线程 | 指定超时时间 | |
队尾插入元素 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
队头取出元素 | remove() | poll() | take() | poll() |
对头取出不删除元素 | element() | peek() | - | - |
//使用样例
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(2);
queue.put(1);
System.out.print(queue.take());
}
参考材料
疯狂Java讲义(第5版)
p757 - 765
Java并发编程:volatile关键字解析
https://www.cnblogs.com/dolphin0520/p/3920373.html