线程间通信的常用方式
1.简介
线程通信简单来说就是实现线程的交替工作,传递信息。例如在一个方法中我有两个线程A和B在运行,我希望线程A先向一个集合里面循环新增数据,当增加到第五次的时候,线程B才开始执行其他的操作。
线程间通信的模型有两种:共享内存和消息传递。
2.共享内存模型
1)volatile关键字
关于volatile关键字的作用详情可以看一下这本篇文章
volatile关键字的作用
使用volatile关键字简单来说就是多个线程同时监听一个变量,当该变量发生变化的时候,所有的监听的线程能够感知。
public class TestSyncVolatile {
// 定义一个共享变量来实现通信,它需要是volatile修饰,否则线程不能及时感知
static volatile boolean notice = false;
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 实现线程A
Thread threadA = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
list.add("abc");
System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 4) {
notice = true;
}
}
});
// 实现线程B
Thread threadB = new Thread(() -> {
while (true) {
if (notice) {
System.out.println("线程B收到通知,开始执行自己的业务...");
System.out.println("此时list中的元素个数为:" + list.size());
break;
}
}
});
// 需要先启动线程B
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 再启动线程A
threadA.start();
}
}
3.消息传递
1)wait()/notify()(notifyAll())方法
wait()/notify()/notifyAll() 方法必须配合 synchronized关键字使用,关于Synchronized关键字的基本使用方法可以看一下这边文章:
Synchronized关键字的基本使用方法
wait()方法会暂时让出同步锁,以便其他正在等待此锁的线程可以得到锁并运行,其他线程调用了notify()/notifyAll()并不会立刻释放锁(notify()是随机释放单个调用当前锁的wait状态的线程,notifyAll()是释放全部调用当前锁的wait状态的线程),而是先等当前线程方法执行完才会释放锁,之后所有等待此锁的线程可以去参与获得锁的竞争,调用当前锁的 wait() 的一个或多个线程会解除 wait 状态,重新参与竞争对象锁, 得到锁的线程会继续执行。
public class TestSyncWait {
public static void main(String[] args) {
//定义一个锁对象
Object lock = new Object();
List<String> list = new ArrayList<>();
// 线程A
Thread threadA = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
synchronized (lock) {
list.add("abc");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5) {
lock.notify();
System.out.println("线程A还没有释放lock的同步锁");
}
}
}
});
//线程B
Thread threadB = new Thread(() -> {
while (true) {
synchronized (lock) {
if (list.size() != 5) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程B收到通知,开始执行自己的业务...");
}
}
});
//需要先启动线程B
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//再启动线程A
threadA.start();
}
}
就比如上文的示例中,先启动线程B,线程B获取lock实例的锁,但是集合中没有5个参数,所以调用wait()方法,释放lock实例的锁,此时线程A获取并执行,当集合中参数个数到达5个时,调用lock.notify()释放lock实例的单个锁,但是此时本次循环还没结束,所以继续先执行完本次循环内容之后才释放释放lock实例的单个锁,之后线程A和线程B共同竞争lock锁,有可能线程A继续抢到锁继续循环,也有可能线程B抢到锁执行他的代码,所以每次启动都可能结果不同。
2)ReentrantLock
使用ReentrantLock和wait()/notify()的大体执行逻辑相同,但还是有不少区别:
wait()/notify():
锁调用notify()方法后,会等当前方法执行完才释放锁。
配合 synchronized关键字使用,synchronized为非公平锁,notify()之后所有等待锁的线程会去竞争获取锁。
ReentrantLock:
锁调用unlock方法后会立即释放锁。
公平锁,结合Condition使用,会释放指定的对象的锁。
public class TestSyncReentrantLock {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
List<String> list = new ArrayList<>();
//线程A
Thread threadA = new Thread(() -> {
lock.lock();
for (int i = 1; i <= 10; i++) {
list.add("abc");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5) {
condition.signal();
lock.unlock();
System.out.println("线程A还没有释放lock的同步锁");
}
}
});
//线程B
Thread threadB = new Thread(() -> {
lock.lock();
if (list.size() != 5) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程B收到通知,开始执行自己的业务...");
lock.unlock();
});
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadA.start();
}
}
3)CountDownLatch
CountDownLatch使用方式与ReentrantLock类似,执行完countDown()方法后,不必等当前线程剩下方法执行完,可直接唤醒等待线程,执行其代码。
public class TestSyncCountDownLatch {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);
List<String> list = new ArrayList<>();
//线程A
Thread threadA = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
list.add("abc");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5) {
countDownLatch.countDown();
System.out.println("线程A其他业务代码");
}
}
});
//线程B
Thread threadB = new Thread(() -> {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程B收到通知,开始执行自己的业务...");
});
//需要先启动线程B
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//再启动线程A
threadA.start();
}
}
4)LockSupport
LockSupport 是一种灵活的实现线程间阻塞和唤醒的工具。他可以根据线程名唤醒指定线程,唤醒之后不必等当前方法执行完,被唤醒线程可立刻执行其代码。
public class TestSyncLockSupport {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//线程B
final Thread threadB = new Thread(() -> {
if (list.size() != 5) {
LockSupport.park();
}
System.out.println("线程B收到通知,开始执行自己的业务...");
});
//线程A
Thread threadA = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
list.add("abc");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5) {
LockSupport.unpark(threadB);
System.out.println("线程A其他业务代码...");
}
}
});
threadA.start();
threadB.start();
}
}