多线程学习笔记05线程协作
线程通信
解决方式1管程法解决方式2信号灯法 线程池
使用线程池
多线程学习笔记05线程协作 线程通信假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费;如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止;如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。
我们来分析一下:
首先,这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费;对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费;在生产者消费者问题中,仅有synchronized是不够的
synchronized可阻止并发更新同一个共享资源,实现了同步
synchronized不能用来实现不同线程之间的消息传递(通信)
java提供了几个方法解决线程之间的通信问题
解决方式1管程法注意:均是Object方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常
并发协作模型“生产者/消费者模式”—>管程法
生产者:负责生产数据的模块(可能是方法,对象,线程,进程);消费者:负责处理数据的模块(可能是方法,对象,线程,进程);缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
//生产者消费者模型,利用缓冲区解决:管程法 public class TestPC { public static void main(String[] args) { SynContainer container = new SynContainer(); new Producer(container).start(); new Consumer(container).start(); } } //生产者 class Producer extends Thread { SynContainer container; public Producer(SynContainer container) { this.container = container; } //生产 @Override public void run() { for (int i = 1; i <= 100; i++) { container.push(new Product(i)); } } } //消费者 class Consumer extends Thread { SynContainer container; public Consumer(SynContainer container) { this.container = container; } //消费 @Override public void run() { for (int i = 1; i <= 100; i++) { container.pop(); } } } //产品 class Product extends Thread { int id;//产品编号 public Product(int id) { this.id = id; } } //缓冲区 class SynContainer { //需要一个容器大小 Product[] products = new Product[10]; //容器计数器 int count = 0; //生产者放入产品 public synchronized void push(Product product) { //如果容器满了,等待消费者消费 if (count == products.length) { //通知消费者消费,生产等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果容器没有满,则丢入产品 products[count] = product; System.out.println("生产了第" + product.id + "个产品"); count++; //可以通知消费者消费了 this.notifyAll(); } //消费者消费产品 public synchronized Product pop() { //判断能否消费 if (count == 0) { //通知生产者生产,消费等待 try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (count > 0) { //可以消费 count--; Product product = products[count]; System.out.println("消费了第" + product.id + "个产品");//每进来一个pop,就减一个产品 //吃完了,通知生产者生产 this.notifyAll(); return product; } else { //如果没有产品,直接通知生产者生产 this.notifyAll(); throw new RuntimeException("商品不够"); } } }
运行结果:
备注:
原出现错误的代码:
//生产者消费者模型,利用缓冲区解决:管程法 public class TestPC { public static void main(String[] args) { SynContainer container = new SynContainer(); new Producer(container).start(); new Consumer(container).start(); } } //生产者 class Producer extends Thread { SynContainer container; public Producer(SynContainer container) { this.container = container; } //生产 @Override public void run() { for (int i = 1; i <= 100; i++) { container.push(new Product(i)); System.out.println("生产了第" + i + "个产品"); } } } //消费者 class Consumer extends Thread { SynContainer container; public Consumer(SynContainer container) { this.container = container; } //消费 @Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println("消费了第" + container.pop().id + "个产品");//每进来一个pop,就减一个产品 } } } //产品 class Product extends Thread { int id;//产品编号 public Product(int id) { this.id = id; } } //缓冲区 class SynContainer { //需要一个容器大小 Product[] products = new Product[10]; //容器计数器 int count = 0; //生产者放入产品 public synchronized void push(Product product) { //如果容器满了,等待消费者消费 if (count == products.length) { //通知消费者消费,生产等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果容器没有满,则丢入产品 products[count] = product; count++; //可以通知消费者消费了 this.notifyAll(); } //消费者消费产品 public synchronized Product pop() { //判断能否消费 if (count == 0) { //通知生产者生产,消费等待 try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //如果可以消费 count--; Product product = products[count]; //吃完了,通知生产者生产 this.notifyAll(); return product; } }
解决方式2信号灯法运行出现产品还没生产就被消费的情况,是因为程序中同时存在了3个线程,主线程、消费者、生产者,而sout语句是写在主线程中的,只要将sout语句放在各自的线程中就可以得到真实的生产-消费顺序
并发协作模型“生产者/消费者模式”—>信号灯法
借助标志位
生产者:负责生产数据的模块(这里的模块可能是:方法、对象、线程、进程)消费者:负责处理数据的模块(这里的模块可能是:方法、对象、线程、进程)缓冲对象:生产者消费者使用同一资源,他们之间有个标志位,类似于信号灯的作用,通过信号灯控制生产者和消费者的循环使用
//信号灯法,通过标志位解决 public class TestPC2 { public static void main(String[] args) { TV tv = new TV(); new Player(tv).start(); new Watcher(tv).start(); } } //生产者:演员 class Player extends Thread { TV tv; public Player(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { if (i % 2 == 0) { this.tv.play("快乐大本营播放中"); } else { this.tv.play("抖音记录美好生活"); } } } } //消费者:观众 class Watcher extends Thread { TV tv; public Watcher(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { tv.watch(); } } } //产品:节目 class TV { //演员表演,观众等待T //观众观看,演员等待F String voice;//表演的节目 boolean flag = true; //表演 public synchronized void play(String voice) { if (!flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("演员表演了:" + voice); //通知观众观看 this.notifyAll();//通知唤醒 this.voice = voice; this.flag = !this.flag;//取反 } //观看 public synchronized void watch() { if (flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("观看了:" + voice); //通知演员表演 this.notifyAll(); this.flag = !this.flag;//取反 } }
运行结果:
线程池演员表演什么,观众就看什么
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理(…)
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
使用线程池
JDK 5.0起提供了线程池相关APl: ExecutorService和ExecutorsExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; //测试线程池 public class TestPool { public static void main(String[] args) { //创建服务,创建线程池 //newFixedThreadPool 参数为线程池大小 ExecutorService service = Executors.newFixedThreadPool(10); //执行 service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //关闭连接 service.shutdown(); } } class MyThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)