一. synchronized 实现线程通讯
- 什么是线程通讯,可以将线程分为生产者线程与消费者线程,生产者线程创建共享数据(写),给消费者线程使用(读)
- 实现线程通讯的基本流程
- 多个线程操作的资源类
- 提供判断当前该生产还是消费的标识
- 生产线程执行,会判断当前是否该生产,不需要,当前线程进入休眠状态,否则执行生产,生产完毕后唤醒消费线程进行消费
- 消费线程执行,会判断当前是否该消费,不需要,当前线程进入休眠状态,否则执行消费,消费完毕后唤醒生产线程继续生产
- wait() 与 notify() 方法讲解
- wati() 与 notify() 是 Object 总服务类提供的方法,
- wati() 与 notify() 使用前提是在 synchronized 中
- wati() 表示休眠并释放锁资源, notify() 表示唤醒当前此对象监视器上(同一锁下的)其它休眠线程
- wait() 与 join() 的区别: wait() 用在 synchronized 中, 被 wait() 的线程需要唤醒, 在一个方法中调用了 join(),表示当前线程进入阻塞状态,并且在其它线程执行完毕后才会自动唤醒执行,没有锁的概念
- wait() 与 sleep() 的区别: sleep 指定休眠时间,时间过后自动唤醒,并且sleep不会释放锁
- notify() 与 yield()区别: yield() 使当前线程重新回到可执行状态,但是并不会马上执行,没有锁的概念,notify() 是唤醒被 wait() 的线程,与 wait() 配合使用的
代码示例
- 多线程同时操作的资源类,注意点在判断标识时不可以使用 if 判断,因为一个线程在进入if 后进行了阻塞,后续再唤醒时,不会再去判断一遍,会造成生产消费,不一致的情况,使用 while 进行判断,当有线程进入 while 中被阻塞,在后续被唤醒会再执行一次 while,再去判断一次
//多线程操作数据流程
//1.多线程操作的资源类
//2.提供用来判断当前该生产还是消费的标识
//3.synchronized 方式下,通过 wait() 与 notify() 配合实现
class MyResource{
//1.num 在当前可以看为是生产消费的数据,
//同时也是判断当前该生产还是消费的标识
private Integer num = 0;
//2.生产方法
public synchronized void production() throws Exception {
//1.通过标识判断,当前是否生产,不可以则进入阻塞状态
while (num != 0){
this.wait();
}
//2.生产
num++;
System.out.println("执行生产"+num);
//3.生产完毕唤醒同一监视器下的其它线程(也就是唤醒消费线程进行消费)
this.notify();
}
//消费方法
public synchronized void consumer() throws Exception{
//1.通过标识判断,当前是否消费,不可以则进入阻塞状态
while (num == 0){
this.wait();
}
//2.消费
num--;
System.out.println("执行消费"+num);
//3.消费完毕,唤醒同一监视器下的其它线程(也就是唤醒生产线程进行生产)
this.notify();
}
}
- 多线程执行生产,消费测试
public class T1 {
public static void main(String[] args) {
//创建资源类对象
MyResource resource = new MyResource();
//1.使用匿名内部类方式创建 A 线程执行生产
new Thread(()->{
try {
for(int i =0;i<50; i++){
resource.production();
}
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
//2.使用匿名内部类方式创建 B 线程执行消费
new Thread(()->{
try {
for(int i =0 ;i<50; i++){
resource.consumer();
}
} catch (Exception e) {
e.printStackTrace();
}
},"B").start();
}
}
二. Lock + Condition 实现线程通讯
- 在生产或消费执行时,首先获取到 Lock 锁,
- 判断标识当前不可以生产,或消费时,通过 Condition 调用 await() 方法设置当前线程进入阻塞状态,相当于 wait()
- 当生产或消费完毕后通过 Condition 调用 signal() 唤醒其它线程
- 注意点: 防止发送异常 Lock 锁无法释放问题,在 finally 中释放锁
代码示例
- 多线程操作的资源类
class MyResource{
//1.num 在当前可以看为是生产消费的数据,
//同时也是判断当前该生产还是消费的标识
private Integer num = 0;
//2.创建 Lock 锁
private Lock lock = new ReentrantLock();
//3.创建 Condition
private Condition condition = lock.newCondition();
//2.生产方法
public void production() throws Exception {
//1.生产时首先获取到锁
lock.lock();
try{
//2.通过标识判断,当前是否生产,不可以则进入阻塞状态
while (num != 0){
condition.await(); //相当于前面的 this.wait();
}
num++; //3.生产
System.out.println("执行生产"+num);
//4.生产完毕唤醒同一监视器下的其它线程(也就是唤醒消费线程进行消费)
condition.signal(); //相当于前面的 this.notify();
}catch(Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//消费方法
public void consumer() throws Exception{
//1.消费时首先获取到锁
lock.lock();
try{
//2.通过标识判断,当前是否消费,不可以则进入阻塞状态
while (num == 0){
condition.await();
}
num--; //3.消费
System.out.println("执行消费"+num);
//4.消费完毕,唤醒同一监视器下的其它线程(也就是唤醒生产线程进行生产)
condition.signal();
}catch(Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
- 多线程生产消费执行测试
public class T1 {
public static void main(String[] args) {
MyResource resource = new MyResource();
//1.使用匿名内部类方式创建 A 线程执行生产
new Thread(()->{
try {
for(int i =0;i<50; i++){
resource.production();
}
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
//2.使用匿名内部类方式创建 B 线程执行消费
new Thread(()->{
try {
for(int i =0 ;i<50; i++){
resource.consumer();
}
} catch (Exception e) {
e.printStackTrace();
}
},"B").start();
}
}
Lock + Condition 实现线程通讯的优点
- 问题: 在前面的生产消费测试中,会发现,A线程生产完毕后,B线程消费,假设同时存在 C 生产线程,生产出的数据要指定的 D线程消费,问题是生产线程执行完毕后如何精准通知到指定的消费线程进行消费
- 解决问题,可以创建多个 Condition,一个 Condition 对应线程,当前指定的生产或消费线程在不可以执行时使用指定的 Condition 调用 await() 进入阻塞,当生产或消费完毕后使用指定的 Condition 调用 signal() 唤醒对应的线程
- 多线程操作的资源类,提供多个 Condition,通过指定的 Condition 在方法中指定进行指定休眠,指定唤醒
class MyResource {
//1.num 在当前可以看为是生产消费的数据,
//同时也是判断当前该生产还是消费的标识
private Integer num = 0;
//2.创建 Lock 锁
private Lock lock = new ReentrantLock();
//3.创建对应 A生产的 Condition
private Condition conditionA = lock.newCondition();
//4.创建对应 B消费 Condition
private Condition conditionB = lock.newCondition();
//5.创建对应 C生产的 Condition
private Condition conditionC = lock.newCondition();
//6.创建对应 D消费 Condition
private Condition conditionD = lock.newCondition();
//A生产方法
public void production() throws Exception {
//1.生产时首先获取到锁
lock.lock();
try {
//1.通过标识判断,当前是否生产,不可以则进入阻塞状态
while (num != 0) {
//相当于前面的 this.wait();
conditionA.await();
}
//2.生产
num++;
System.out.println("A执行生产" + num);
//3.生产完毕唤醒同一监视器下的其它线程(也就是唤醒消费线程进行消费)
//相当于前面的 this.notify();
conditionB.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//C生产方法
public void productionC() throws Exception {
//1.生产时首先获取到锁
lock.lock();
try {
//1.通过标识判断,当前是否生产,不可以则进入阻塞状态
while (num != 0) {
//相当于前面的 this.wait();
conditionC.await();
}
//2.生产
num++;
System.out.println("C执行生产" + num);
//3.生产完毕唤醒同一监视器下的其它线程(也就是唤醒消费线程进行消费)
//相当于前面的 this.notify();
conditionD.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//B消费方法
public void consumerB() throws Exception {
//1.消费时首先获取到锁
lock.lock();
try {
//1.通过标识判断,当前是否消费,不可以则进入阻塞状态
while (num == 0) {
conditionB.await();
}
//2.消费
num--;
System.out.println("B执行消费" + num);
//3.消费完毕,唤醒同一监视器下的其它线程(也就是唤醒生产线程进行生产)
conditionA.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//D消费方法
public void consumerD() throws Exception {
//1.消费时首先获取到锁
lock.lock();
try {
//1.通过标识判断,当前是否消费,不可以则进入阻塞状态
while (num == 0) {
conditionD.await();
}
//2.消费
num--;
System.out.println("D执行消费" + num);
//3.消费完毕,唤醒同一监视器下的其它线程(也就是唤醒生产线程进行生产)
conditionC.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
- 多线程生产,消费运行测试
public class T1 {
public static void main(String[] args) {
MyResource resource = new MyResource();
//1.使用匿名内部类方式创建 A 线程执行生产
new Thread(() -> {
try {
for (int i = 0; i < 3; i++) {
resource.production();
}
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();
//2.使用匿名内部类方式创建 B 线程执行消费
new Thread(() -> {
try {
for (int i = 0; i < 3; i++) {
resource.consumerB();
}
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
//3.使用匿名内部类方式创建 C 线程执行生产
new Thread(() -> {
try {
for (int i = 0; i < 3; i++) {
resource.productionC();
}
} catch (Exception e) {
e.printStackTrace();
}
}, "C").start();
//4.使用匿名内部类方式创建 B 线程执行消费
new Thread(() -> {
try {
for (int i = 0; i < 3; i++) {
resource.consumerD();
}
} catch (Exception e) {
e.printStackTrace();
}
}, "D").start();
}
}