​目录

一、什么是线程通信?

二、线程通信的实现

1.使用volatile实现线程通信

2.使用wait/notify实现线程通信

3.使用ReentrantLock创建Condition实现

4.通过LockSupport实现线程通信

5.使用CountDownLatch实现线程通信


一、什么是线程通信?

线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能称为一个整体,线程间的通信就成为整体的必用方式之一。

当线程存在通信指挥,系统间的交互性会更强大,在提高CPU利用率的同时,还会使开发人员在处理线程任务的过程中有效的把控和监督。

二、线程通信的实现

1.使用volatile实现线程通信

如下列代码所示,list使用volatile修饰,利用volatile的可见性,当线程t1添加了5个元素的时候,t2得到通知!

弊端:需要不断地做while循环,影响性能!

public class ThreadCommunication {
private volatile static List list = new ArrayList();

public static void main(String[] args) {
final ThreadCommunication ThreadCommunication = new ThreadCommunication();
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
try {
for (int i = 0; i < 10; i++) {
list.add("test");
System.out.println("当前线程" + Thread.currentThread().getName() + "添加了一个元素");
Thread.sleep(500);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}, "t1");

Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
if(list.size() == 5){
System.out.println("当前线程" + Thread.currentThread().getName() + "收到通知,list数量为5");
throw new RuntimeException();
}
}
}

}, "t2");

t2.start();
t1.start();
}
}

2.使用wait/notify实现线程通信

wait和notify都是Object类的方法,而Object是所有类的父类,所以,java为所有的对象提供了这两个方法。

1、wait和notify必须配合synchronized使用
2、wait方法释放锁,notify方法不释放锁

如下列代码所示,使用wait、notify结合synchronized实现线程通信。

弊端:不能实时获得锁,下面的代码运行之后,t1的循环做完t2才得到通知。

public class ThreadCommunication2 {
private static List list = new ArrayList();

public static void main(String[] args) {
final Object lock = new Object();

Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
try {
synchronized(lock){
for (int i = 0; i < 10; i++) {
list.add("test");
System.out.println("当前线程" + Thread.currentThread().getName() + "添加了一个元素");
Thread.sleep(500);
if(list.size() == 5){
System.out.println("已经发出通知");
lock.notify();
}
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}, "t1");

Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(lock){
if(list.size() != 5){
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("当前线程" + Thread.currentThread().getName() + "收到通知,list数量为5");
throw new RuntimeException();
}
}

}, "t2");

t2.start();
t1.start();
}
}

3.使用ReentrantLock创建Condition实现

Conditon中的await()对应Object的wait()

Condition中的signal()对应Object的notify()

Condition中的signalAll()对应Object的notifyAll()

这种方法跟 Object 的 wait和 notify 一样,不能实时获得锁,下面的代码运行之后,t1的循环做完t2才得到通知。

public class ThreadCommunication3 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

List<String> list = new ArrayList<>();

Thread t1 = new Thread(() -> {
lock.lock();
for (int i = 1; i <= 10; i++) {
list.add("abc");
System.out.println("线程1向list中添加一个元素,此时list中的元素个数为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5){
condition.signal();
}

}
lock.unlock();
});
// 实现线程B
Thread t2 = new Thread(() -> {
lock.lock();
if (list.size() != 5) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程2收到通知...");
lock.unlock();
});
t2.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.start();
}
}

4.通过LockSupport实现线程通信

​LockSupport​​是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法。

线程通信主要用到两个函数:

park:英文意思为停车。我们如果把Thread看成一辆车的话,park就是让车停下

unpark:就是让车启动然后跑起来 

弊端:需要知道被唤起的线程名字

public class ThreadCommunication4 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();

final Thread t2 = new Thread(() -> {
if (list.size() != 5) {
//线程阻塞
LockSupport.park();
}
System.out.println("线程2收到通知...");
});
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
list.add("abc");
System.out.println("线程1向list中添加一个元素,此时list中的元素个数为:" + list.size());
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
if (list.size() == 5){
//唤起线程t2
LockSupport.unpark(t2);
}
}
});
t1.start();
t2.start();
}
}

5.使用CountDownLatch实现线程通信

CountDownLatch是concurrent包里面的一个类,CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。当每一个线程完成自己任务后,计数器的值就会减1。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以唤醒其他线程,继续执行接下来的任务。

await()函数:此函数将会使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。

countDown函数:此函数将递减锁存器的计数,如果计数到达零,则释放所有等待的线程

如下列代码所示,t1中使用countDown方法之后,计数器会减到0,这时会立即唤醒t2线程,然后继续执行t2线程的代码

public class ThreadCommunication5 {
private static List list = new ArrayList();

public static void main(String[] args) {
final CountDownLatch cdl = new CountDownLatch(1);//设置一个计数器

Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
try {
for (int i = 0; i < 10; i++) {
list.add("test");
System.out.println("当前线程" + Thread.currentThread().getName() + "添加了一个元素");
Thread.sleep(500);
if(list.size() == 5){
System.out.println("已经发出通知");
cdl.countDown();//计数器变为0,立即唤醒t2线程
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}, "t1");

Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
if(list.size() != 5){
try {
cdl.await();//在计数器等于0之前,会一直处于等待状态
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("当前线程" + Thread.currentThread().getName() + "收到通知,list数量为5");
throw new RuntimeException();
}
}, "t2");

t2.start();
t1.start();
}
}


参考文献:

[1].CountDownLatch

[2].java中Condition类的详细介绍

[3].LockSupport的用法及原理

[4].什么时候用CountDownLatch?