等待与唤醒案例、线程池
等待唤醒机制
线程间通信
1、概念
多个线程在处理同一个资源,但处理的动作(线程的任务)各不相同
2、例如
线程A:生产包子
线程B:吃包子
包子可以理解为同一资源,线程A和线程B,一个生产一个消费,那么线程A和线程B之间就存在线程通信问题

3、为什么要处理线程间通信
多个线程并发执行,在默认情况下CPU是随机切换线程的。
当我们需要多个线程共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间就需要一些协调通信
4、如何保证线程间通信有效利用资源
多个线程在处理同一资源,并且任务不同时,需要线程通信帮助我们解决线程之间对同一个变量的使用操作。也就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。我就需要通过一定手段使各个线程能有效的利用资源。而这种手段就是---线程的唤醒机制
等待唤醒机制
1、什么是等待唤醒机制
这是多个线程间的一种协作机制。
谈到线程我们通常会想到线程间抢到CPU资源的竞争,比如争夺锁,但这并不代表他们只会去争夺,线程间也会有协作机制,相对于竞争,他们更多的是一起合作完成某些任务。
例如:

当一个线程进行了规定操作后,就会进入等待状态(wait()等待执行任务),等待其他线程执行完他们的指定代码后(其他人完成任务后),再将其唤醒(notify()呼叫队友执行任务);
在多个线程进行等待时,如果需要,可以使用notifyAll()唤醒所有等待线程(呼叫全员---例如H.M事件~)。
wait/notify就是线程间的一种协作机制。
2、等待唤醒中的方法
1.wait();:线程不再活动,不参与调度,进入wait set中,因此不会浪费CPU资源也不会去竞争锁。
这时当前线程状态是Waiting,它还要等待别的线程执行一个特定动作,也就是"通知(notify)"在这个对象上等待的线程从wait set中释放出来,重新进入调度队列中。
2.notify();:选取所通知对象的wait set中的一个线程释放。
好比餐馆有空位,先让等待就餐最久的顾客先入座
3.notifyAll();:唤醒所有线程。
3、注意:
哪怕只通知了一个等待线程,被通知的线程也不会立马恢复执行,因为当初中断的地方在同步代码块内,而此刻它已经不持有锁,所以他需要再次尝试获取锁(或面临其他线程的竞争),成功后才能在当初的wai()方法之后的地方恢复
如果能获取锁,线程就从Waiting--->Runnable状态
否则,从wait set出来,又进入entry set,线程就从Waiting状态又变成Blocked(阻塞状态)
4、调用wait和notify方法需要注意的细节
1.wait方法与notify方法必须由同一个锁对象调用。
对应的锁对象可以 通过notify唤醒使用同一个锁对象调用的wait方法后的线程(一把锁只能开一个或者关同一个门)
2.wait方法与notify方法属于Object类的方法。
锁对象可以是任意对象,而任意对象的所属类都是继承Object类
3.wait方法和notify方法必须在同步代码块或者同步函数(方法?)中使用
必须通过锁对象调用这2个方法。
生产者和消费者问题
1、流程图:

2、代码演示
package day07; public class Baozi { //成员变量 皮、馅、是否有包子 String pi; String xian; //默认没有 boolean isBZ=false; }
package day07; /** * 注意: * 包子铺线程和包子类线程的关系--->通信(互斥) * 必须同时同步技术保证两个线程只能有一个在执行 * 锁对象必须保证唯一,可以使用包子作为锁对象 * 包子铺类和吃货类就需要把包子作为参数传递进来 * 1、在成员位置创建包子变量 * 2、使用带参构造方法,为包子变量赋值 */ public class BaoZiPu extends Thread { //1、在成员位置创建包子变量 private Baozi baozi; //使用带参构造方法,为包子变量赋值 public BaoZiPu(Baozi bz){ super(); this.baozi = bz; } //设置线程任务:生产包子 @Override public void run() { //定义一个变量 int count = 0; //必须同时同步技术保证两个线程只能有一个在执行 synchronized (baozi) { //让包子铺一直生产包子 while (true) { //判断有没有包子 if (baozi.isBZ == true) { //有包子 进入等待状态 try { baozi.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } if (count % 2 == 0) { //生产 灌汤包 baozi.pi = "薄皮"; baozi.xian = "三鲜汤"; } else { //生产叉烧包 baozi.pi = "薄皮"; baozi.xian = "叉烧肉"; } count++; System.out.println("包子铺正在生产:" + baozi.pi + baozi.xian + "包子"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //生产完包子 状态为true baozi.isBZ = true; //唤醒吃货 吃包子 baozi.notify(); System.out.println("包子铺已经生产好了" + baozi.pi + baozi.xian + "包子,吃货可以吃了"); System.out.println("~~~~~~~~~~~~~~~~~上包子~~~~~~~~~~~~~~~~~~~~~~~"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
package day07; public class ChiHuo extends Thread{ private Baozi baozi; public ChiHuo(Baozi bz){ super(); this.baozi = bz; } //执行线程任务 @Override public void run() { //保证同时同步执行技术保证两个线程只能有一个执行 synchronized (baozi){ while (true){ synchronized (baozi){ if(!baozi.isBZ ==true){ //没有包子 try { baozi.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //被唤醒后 执行代码吃包子 System.out.println("吃货正在吃"+baozi.pi+baozi.xian+"包子"); try { //留点时间给他吃包子 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //吃货吃完包子 修改包子状态 baozi.isBZ = false; //唤醒包子铺线程 生产包子 baozi.notify(); System.out.println("吃货已经把"+baozi.pi+baozi.xian+"包子吃完啦!"); System.out.println("---------------------------------------------"); } } } } }
package day07; public class CeShi { public static void main(String[] args) { //创建包子对象 Baozi baozi = new Baozi(); //开启包子铺线程 生产包子 new BaoZiPu(baozi).start(); //开启吃货线程 吃包子 new ChiHuo(baozi).start(); } }
线程池
线程池概述
我们使用线程的时候,就需要创建一个线程,这样实现起来非常简便,但存在一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建和销毁线程需要时间。
那么有没有一种办法使得线程可以复用?就是执行完一个任务,并不被销毁,而是可以继续执行其他任务。
在java中可以通过线程池来达到这种效果。
例如:
利用之前学的知识模拟线程池

线程池概念
线程池--->存储线程的容器
线程池
其实就是一个容纳多个线程的容器。其中的线程可以反复使用,省去频繁创建线程对象的操作,以减少资源的消耗
线程池工作原理图

合理利用线程池的好处:
1.降低资源消耗。减少了创建和消耗线程的次数,每个工作线程可以反复利用,执行多个任务。
2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3.提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线程的数目,防止消耗过多内存。
线程池的使用
线程池:jdk1.5之后提供的。
java.util.concurrent.Excutors:线程池的工厂类,用来生成线程池。
1、Excutors类中的静态方法:
static ExcutorService new FixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池。
参数:
int nThreads:创建线程池中包含的线程数量
返回值:
ExcutorService接口:返回的是ExcutorService接口的实现类对象,我们可以用ExcutorService接口接收(面向接口编程)
java.util.concurrent.ExcutorService:线程池接口
用来从线程池获取线程,调用start方法,执行线程
submit(Runnable task):提交一个Runnable任务用于执行
关闭/销毁线程池方法
void Shutdown();
2、使用步骤
1.使用线程池的工厂类Excutors里面提供的静态方法---newFixedThreadPool生产一个指定线程数量的线程池
2.创建一个,实现Runnable接口,重写run方法,设置线程任务
3.调用ExcutorService中的submit()方法,传递线程任务(实现类),开启线程,执行run方法
4.调用ExcutorService中的shutdown()方法,销毁线程池(不建议)
3、例如
package day07.day07_1; //创建Runnable实现类 public class RunnableImpl implements Runnable{ //重写run方法 @Override public void run() { System.out.println(Thread.currentThread().getName()+"-->创建了一个新的线程并执行"); } }
package day07.day07_1; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Demo01 { public static void main(String[] args) { //1、创建线程池 里面包含3个线程 ExecutorService pools = Executors.newFixedThreadPool(3); //3.创建Runnable的实现类对象 RunnableImpl run = new RunnableImpl(); //4.调用线程池的submit方法 提交线程任务 pools.submit(run);//pool-1-thread-2-->创建了一个新的线程并执行 pools.submit(run);//pool-1-thread-1-->创建了一个新的线程并执行 pools.submit(run);//pool-1-thread-3-->创建了一个新的线程并执行 //pool-1-1-thread-2 池1中的线程1 2 3都执行了 } }
注意:
1.线程池会一直开启,使用完线程会把线程归还给线程池,线程可以继续使用
2.如果使用shutdown线程池会被销毁,也就不难继续使用.submit方法。

浙公网安备 33010602011771号