JUC高并发编程
三、线程间通信
线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。
3.1)线程间通信案例
两个线程,一个线程对当前数值加1,另一个线程对当前数值减1,要求用线程间通信
3.1.1)synchronized 方案
package com.study.sync;
//第一步 创建资源类,定义属性和操作方法
class Share {
//初始值
private int number = 0;
//+1的方法
public synchronized void incr() throws InterruptedException {
//第二步 判断 干活 通知
//判断:number值是否是0,如果不是0,等待
// if (number != 0) { --> 会产生虚假唤醒问题,即只对第一次进入的条件进行了判断,使用while判断可以避免虚假唤醒
while (number != 0) {
//在哪里睡,就在哪里醒
this.wait();
}
//干活:如果number值是0,就+1操作
number++;
System.out.println("当前线程名:" + Thread.currentThread().getName() + " :: " + number);
//通知:通知其他线程
this.notifyAll();
}
//-1的方法
public synchronized void decr() throws InterruptedException {
//判断:number值是否是1,如果不是1,等待
// if (number != 1) { --> 会产生虚假唤醒问题,即只对第一次进入的条件进行了判断,使用while判断可以避免虚假唤醒
while (number != 1) {
this.wait();
}
//干活:如果number值是1,就-1操作
number--;
System.out.println("当前线程名:" + Thread.currentThread().getName() + " :: " + number);
//通知:通知其他线程
this.notifyAll();
}
}
public class ThreadDemo1 {
//第三步 创建多个线程,调用资源类的操作方法
public static void main(String[] args) {
Share share = new Share();
//创建线程
// 创建线程 AA
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
share.incr(); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "AA").start();
// 创建线程 BB
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
share.decr(); //-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "BB").start();
// 创建线程 CC
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
share.incr(); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "CC").start();
// 创建线程 DD
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
share.decr(); //-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "DD").start();
}
}
输出:
com.study.sync.ThreadDemo1
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:CC :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:CC :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:AA :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
3.1.2)Lock 方案
package com.study.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//第一步 创建资源类,定义属性和操作方法
class Share {
private int number = 0;
//创建Lock
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//+1 方法
public void incr() throws InterruptedException {
//上锁
lock.lock();
try {
//判断:number值是否是0,如果不是0,等待
// if (number != 0) { --> 会产生虚假唤醒问题,即只对第一次进入的条件进行了判断,使用while判断可以避免虚假唤醒
while (number != 0) {
condition.await();
}
//干活:如果number值是0,就+1操作
number++;
System.out.println("当前线程名:" + Thread.currentThread().getName() + " :: " + number);
//通知:通知其他线程
condition.signalAll();
} finally {
//解锁
lock.unlock();
}
}
//-1 方法
public void decr() throws InterruptedException {
//上锁
lock.lock();
try {
//判断:number值是否是1,如果不是1,等待
// if (number != 1) { --> 会产生虚假唤醒问题,即只对第一次进入的条件进行了判断,使用while判断可以避免虚假唤醒
while (number != 1) {
condition.await();
}
//干活:如果number值是1,就-1操作
number--;
System.out.println("当前线程名:" + Thread.currentThread().getName() + " :: " + number);
//通知:通知其他线程
condition.signalAll();
} finally {
//解锁
lock.unlock();
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
Share share = new Share();
// 创建线程 AA
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
// 调用 +1 方法
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "AA").start();
// 创建线程 BB
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
// 调用 -1 方法
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "BB").start();
// 创建线程 CC
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
// 调用 +1 方法
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "CC").start();
// 创建线程 DD
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
// 调用 -1 方法
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "DD").start();
}
}
输出:
com.study.lock.ThreadDemo2
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:AA :: 1
当前线程名:BB :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
当前线程名:CC :: 1
当前线程名:DD :: 0
3.2)多线程编程步骤
- 创建资源类,在资源类中创建属性和操作方法
- 在资源类中操作方法【判断 、干活 、通知 】
- 创建多个线程,调用资源类的操作方法
- 防止虚假唤醒问题
该博客探讨了Java中两种线程间通信的实现方式:synchronized关键字和Lock接口。通过一个数值加减的案例展示了如何使用这两种方式确保线程安全,防止虚假唤醒问题,并详细解释了每个方法的步骤和逻辑。
1万+

被折叠的 条评论
为什么被折叠?



