目录
线程间的通信
多线程的优势是提高cpu的利用率,但使用时需要注意:当执行时间比较长的任务时,可能存在线程安全的问题,在以上的前提下,需要保证线程的执行有一定的顺序性,因此就有了线程通信的概念.
线程通信的概念
线程通信,就是一个以线程通知的方式,唤醒某些等待的线程(也可以在某些条件下,让线程进行等待),这样就可以让线程间通过通信的方式满足一定的顺序性.
线程通信的使用方式
synchronized(某个对象) { //对"某个对象"申请加锁,多个线程对同一个对象加锁是同步互斥的
while(如果不满足执行条件) {
某个对象.wait();//释放某个对象的锁,让当前线程进行等待
}
//满足执行条件
执行业务;
某个对象.notify() / 某个对象.notifyAll();//在当前线程释放锁以后以通知的方式唤醒之前调用wait等待的线程
}
对于上述使用方式的解析
· synchronized(某个对象):对某个对象申请加锁,多个线程对同一个对象加锁就是同步互斥的.
· 同步互斥:一个时间点最多只有一个线程申请锁成功,进入synchronized内,执行同步代码.
· 某个对象.wait():释放某个对象的锁,让当前线程进行等待
· 某个对象.notify():在当前线程释放锁以后以通知的方式,唤醒之前调用wait等待的线程.
①notify()是随机唤醒一个,②notifyAll()是唤醒全部
wait()-让线程进行等待
某个线程调用wait方法后,wait做的事情:
①使当前执行代码的线程进行等待.(把线程放在等待队列中)
②释放当前的锁
③满足一定条件时被唤醒,重新尝试获取这个锁
wait需要搭配synchronized来使用,否则使用wait会直接抛出异常
wait结束等待的条件:
①其他线程调用该对象的notify()方法
②wait等待时间超时(wait(1000)类似这种使用)
③其他线程调用该等待线程的interrupted方法,导致wait抛出异常
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
synchronized (lock) {
System.out.println("等待中");
lock.wait();
System.out.println("等待结束");
}
}
notify()-唤醒等待的线程
notify()方法是唤醒等待中的线程
· notify方法也要在同步方法或同步代码块中调用,该方法是用来通知那些可能等待该对象的对象锁的其他线程,对其发出通知notify,并使它们重新获取该对象的对象锁.
· 如果有多个线程目前都处于等待状态,则由线程调度器随机挑出一个呈wait状态的线程(无"先来后到原则").
· 在notify方法调用后,当前线程不会立刻释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁
notify方法使用示例
public class UseDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized(lock) {
System.out.println("wait 等待中");
lock.wait();
System.out.println("wait 等待结束");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("notify 开始通知");
lock.notify();
System.out.println("notify 通知结束");
}
}
}).start();
}
}
notifyAll()-唤醒等待中的线程
notify方法是随机唤醒一个等待中的线程,而notifyAll方法是一次性唤醒所有等待中的线程,这些被唤醒的线程再进行竞争锁.
使用示例:
public class UseDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized(lock) {
System.out.println("wait 等待中");
lock.wait();
System.out.println("wait 等待结束");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
for (int i = 0; i < 6; i++) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "notify 开始通知");
lock.notifyAll();
System.out.println(Thread.currentThread().getName() + "notify 通知结束");
}
}
},"线程"+(i+1)).start();
}
}
}
通过运行结果可以看出,notifyAll()唤醒所有等待中的线程后,这些被唤醒的线程再进行竞争锁,而不是按照线程定义顺序来执行.
线程通信的应用案例
下述代码演示wait()与notifyAll方法的综合使用
案例描述:
· 有3个面包师傅,每个师傅每次生产5个,需要一直生产,当库存大于100时不再生产
· 有10个消费者,每个消费者每次购买2个,需要一直消费,当库存小于0时不再售卖
完整代码
package threadcommunity;
/**
* Created with IntelliJ IDEA.
* Description: 线程通信的使用案例,模拟面包店买卖面包
* User: Li_yizYa
* Date: 2022—05—15
* Time: 23:53
*/
/**
* 业务描述:
* 3个面包师傅,每个师傅每次生产5个,需要一直生产
* 10个消费者,每个消费者每次消费2个,需要一直消费
*/
public class Bakery {
//共享变量,当前面包的库存数,默认初始化为0
private static int STORE;
private static final Object lock = new Object();
public static void main(String[] args) {
//模拟面包师傅生产面包
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//一直生产
while (true){
try {
//准备生产
synchronized (lock){
//不满足生产条件
while (STORE + 5 > 100){
lock.wait();
}
//满足生产条件
STORE += 5;//生产
System.out.printf("%s 生产了5个面包,当前库存:%s\n",
Thread.currentThread().getName(),
STORE);
lock.notifyAll();
Thread.sleep(1000);//间隔一段时间打印
}
Thread.sleep(100);//涉及jvm对synchronized优化,暂时不管
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "面包师傅["+(i+1)+"]").start();
}
//模拟消费者购买面包
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//一直消费
while(true) {
try {
//准备消费
synchronized(lock) {
//不满足消费条件
while(STORE-2 < 0) {
lock.wait();
}
//满足消费条件
STORE -= 2;
System.out.printf("%s 购买了两个面包,当前库存: %d\n",
Thread.currentThread().getName(),
STORE);
lock.notifyAll();
Thread.sleep(1000);
}
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"消费者["+(i+1)+"]").start();
}
}
}
运行结果
程序在一直循环执行,并且通过线程通信使得运行结果一直满足案例的要求