线程间通信
线程间通信的模型有两种:共享内存和消息传递。 场景: 两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求用线程间通信 多线程编程步骤:
- 创建资源类, 在资源类创建属性和操作方法
- 在资源类操作方法
- 判断
- 干活
- 通知
- 创建多个线程,调用资源类的操作方法
- 防止虚假唤醒问题
1. synchronized方案
java
public class Thread_Synchronized_Communicate {
// 第一步 创建资源类, 在资源类创建属性和操作方法
static class Resource {
private int num = 0;
// 第二步 在资源类操作方法
public synchronized void incr() throws InterruptedException {
// 判断
if (num != 0) {
this.wait(); // 在哪里睡就在哪里醒
}
// 干活
num++;
System.out.println(Thread.currentThread().getName() + "执行加 1 操作, num: " + num);
// 通知
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
// 判断
if (num != 1) {
this.wait(); // 在哪里睡就在哪里醒
}
// 干活
num--;
System.out.println(Thread.currentThread().getName() + "执行减 1 操作, num: " + num);
// 通知
this.notifyAll();
}
}
public static void main(String[] args) {
Resource resource = new Resource();
// 第三步 创建多个线程,调用资源类的操作方法
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.incr();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "生产者线程1").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.decr();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "消费者线程2").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.incr();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "生产者线程3").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.decr();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "消费者线程4").start();
}
}
运行结果: 可以看到执行到后面就出现问题了,原因就是存在虚假唤醒问题,所谓虚假唤醒就是线程被唤醒时候,即使资源条件不满足,仍然被错误的被执行了下去。
wait()
在被唤醒时执行特点是会接着执行下一行代码。比如 生产者线程1 执行后释放锁,生产者线程3 拿到锁,num此时是1不是0,此时条件不满足,于是执行wait()
被阻塞同时释放锁, 锁释放出去又被 生产者线程3 拿到,生产者线程3 被唤醒,它不会再去上一行执行条件判定,直接执行下面的代码,包括num++操作。解决办法就是将逻辑判断if改为while, 强行进行逻辑判定。
将if改成while后:
java
public class Thread_Synchronized_Communicate {
// 第一步 创建资源类, 在资源类创建属性和操作方法
static class Resource {
private int num = 0;
// 第二步 在资源类操作方法
public synchronized void incr() throws InterruptedException {
while (num != 0) { // 使用while避免虚假唤醒
this.wait();
}
// 干活
num++;
System.out.println(Thread.currentThread().getName() + "执行加 1 操作, num: " + num);
// 通知
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
while (num != 1) { // 使用while避免虚假唤醒
this.wait();
}
// 干活
num--;
System.out.println(Thread.currentThread().getName() + "执行减 1 操作, num: " + num);
// 通知
this.notifyAll();
}
}
public static void main(String[] args) {
Resource resource = new Resource();
// 第三步 创建多个线程,调用资源类的操作方法
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.incr();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "生产者线程1").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.decr();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "消费者线程2").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.incr();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "生产者线程3").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.decr();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "消费者线程4").start();
}
}
运行结果:
2. Lock方案
使用Condition对象也可以实现通知等待功能
java
public class Thread_ReentrantLock_Communicate {
// 第一步 创建资源类, 在资源类创建属性和操作方法
static class Resource {
ReentrantLock rl = new ReentrantLock();
Condition condition = rl.newCondition();
private int num = 0;
// 第二步 在资源类操作方法
public void incr() throws InterruptedException {
rl.lock();
try{
// 判断
while (num != 0) { // 使用while避免虚假唤醒
condition.await();
}
// 干活
num++;
System.out.println(Thread.currentThread().getName() + "执行加 1 操作, num: " + num);
// 通知
condition.signalAll();
}finally {
rl.unlock();
}
}
public void decr() throws InterruptedException {
rl.lock();
try{
// 判断
while (num != 1) { // 使用while避免虚假唤醒
condition.await();
}
// 干活
num--;
System.out.println(Thread.currentThread().getName() + "执行减 1 操作, num: " + num);
// 通知
condition.signalAll();
}finally {
rl.unlock();
}
}
}
public static void main(String[] args) {
Resource resource = new Resource();
// 第三步 创建多个线程,调用资源类的操作方法
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.incr();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "生产者线程1").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.decr();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "消费者线程2").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.incr();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "生产者线程3").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.decr();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "消费者线程4").start();
}
}
运行结果:
3. 线程间定制化通信
需求: A线程打印2次A,B线程打印5次B,C线程打印10次C,按照此顺序循环10轮。
Synchronized不再适合此精细场景了,因为它不能指定唤醒那个线程。要求是ABC轮流打印。
实现思路:
java
ublic class Thread_ReentrantLock_Communicate_DIY {
// 第一步 创建资源类, 在资源类创建属性和操作方法
static class Resource {
// 可重入锁
ReentrantLock rl = new ReentrantLock();
// 三把钥匙
Condition c1 = rl.newCondition();
Condition c2 = rl.newCondition();
Condition c3 = rl.newCondition();
private int flag = 1;
// 第二步 在资源类操作方法
public void printConsole2() throws InterruptedException {
rl.lock();
try{
while (flag != 1) {
c1.await();
}
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+"打印内容: A"+(i+1) );
}
// 干活
flag = 2;
// 通知
c2.signal();// 唤醒B线程
}finally {
rl.unlock();
}
}
public void printConsole5() throws InterruptedException {
rl.lock();
try{
while (flag != 2) {
c2.await();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"打印内容: B"+(i+1) );
}
// 干活
flag = 3;
// 通知
c3.signal();// 唤醒C线程
}finally {
rl.unlock();
}
}
public void printConsole10() throws InterruptedException {
rl.lock();
try{
while (flag != 3) {
c3.await();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"打印内容: C"+(i+1) );
}
// 干活
flag = 1;
c1.signal();// 唤醒C线程
}finally {
rl.unlock();
}
}
}
public static void main(String[] args) {
Resource resource = new Resource();
// 第三步 创建多个线程,调用资源类的操作方法
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.printConsole2();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "A").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.printConsole5();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "B").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
resource.printConsole10();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "C").start();
}
}
运行结果: