生产者消费者模式
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件.
- 对于生产者,没有生产产品之前,要通知消费者等待.而生产了产品之后,又需要马上通知消费者消费
- 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费,
- 在生产者消费者问题中,仅有synchronized是不够的
- synchronized 可阻止并发更新同一个共享资源,实现了同步
- synchronized不能用来实现不同线程之间的消息传递(通信)
Java提供了几个方法解决线程之间的通信问题
| 方法名 | 作用 |
|---|---|
| wait() | 表示线程一直等待,直到其他线程通知,与sleep不同会释放锁 |
| wait(long timeout) | 指定等待的毫秒数 |
| notify() | 唤醒一个处于等待状态的线程 |
| notifyAII() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
管程法
生产者消费者模型利用缓冲区解决
public class TestPC {public static void main(String[] args) {SynContainer s = new SynContainer();new Producer(s).start();new Consumer(s).start();}
}class Producer extends Thread {SynContainer container;public Producer(SynContainer container) {this.container = container;}@Overridepublic void run() {for (int i = 0; i < 100; i++) {container.addChicken(new Chicken(i));System.out.println("生产了"+i+"只鸡");}}
}class Consumer extends Thread {SynContainer container;public Consumer(SynContainer container) {this.container = container;}@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("消费了"+container.removeChicken().id+"只鸡");}}
}class Chicken {int id;Chicken(int id) {this.id = id;}
}class SynContainer {// 制定容器大小Chicken[] chickens = new Chicken[10];// 计数器int count = 0;// 生产者放入产品public synchronized void addChicken(Chicken chicken) {while (count == chickens.length) {// 通知消费者消费,生产等待try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}chickens[count++] = chicken;// 通知消费this.notifyAll();}public synchronized Chicken removeChicken() {while (count == 0) {// 等待生产try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}Chicken chicken = chickens[--count];// 通知生产this.notifyAll();return chicken;}}
在wait()的使用场景中,必须用while循环包裹条件判断,而非if。这是多线程编程的经典规范,目的是:
应对虚假唤醒,确保线程不会在条件不满足时错误执行;
处理多线程竞争下,唤醒后条件已失效的情况,保证逻辑正确性。
信号灯法
// 信号灯法,标志位解决
public class TestPC2 {public static void main(String[] args) {TV tv = new TV();new Actor(tv).start();new Audience(tv).start();}
}// 生产者 -> 演员
class Actor extends Thread {TV tv;Actor(TV tv) {this.tv = tv;}@Overridepublic void run() {for (int i = 0; i < 20; i++) {if (i % 2 == 0) {this.tv.play("java...");} else {this.tv.play("golang... ");}}}}
// 消费者 -> 观众
class Audience extends Thread {TV tv;Audience(TV tv) {this.tv = tv;}@Overridepublic void run() {for (int i = 0; i < 20; i++) {this.tv.watch();}}
}
// 产品 -> 节目
class TV {// 拍电影 观众等, 看电影 演员等String voice;boolean flag = true; // true -> 没有电影// 表演public synchronized void play(String voice) {while (!flag) { // 注意:这里是!flag,因为flag=false表示已有节目,需等待消费try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("playing " + voice);// 通知观看this.voice = voice;this.flag = false;this.notifyAll(); // 锁在方法或代码块执行执行完后才释放,所以通知也可以放在前面}// 观看public synchronized void watch() {while (this.flag) {try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}// 通知表演System.out.println("watching " + this.voice);this.flag = true;this.notifyAll();}
}
线程池
- 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
- 好处:
- 提高响应速度 (减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)便于线程管理(..)
- corePoolSize:核心池的大小maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TestPool {public static void main(String[] args) {// 创建线程池,制定参数大小ExecutorService pool = Executors.newFixedThreadPool(10);// 执行pool.execute(new myThread());pool.execute(new myThread());pool.execute(new myThread());pool.execute(new myThread());pool.execute(new myThread());// 关闭pool.shutdown();}
}class myThread extends Thread {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}