Tips:样式蚂蚁森林浇水get

等待与唤醒案例、线程池

等待唤醒机制

线程间通信

  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;

}
BaoZi类
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();
                }
            }
        }
    }
}
BaoZiPu类
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("---------------------------------------------");
                }
            }
        }
    }
}
ChiHuo类
package day07;

public class CeShi {
    public static void main(String[] args) {
        //创建包子对象
        Baozi baozi = new Baozi();
        //开启包子铺线程 生产包子
        new BaoZiPu(baozi).start();
        //开启吃货线程 吃包子
        new ChiHuo(baozi).start();
    }
}
CeShi类

 

 线程池

线程池概述

 

  我们使用线程的时候,就需要创建一个线程,这样实现起来非常简便,但存在一个问题

  如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建和销毁线程需要时间。

  那么有没有一种办法使得线程可以复用就是执行完一个任务,并不被销毁,而是可以继续执行其他任务

  在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()+"-->创建了一个新的线程并执行");
    }
}
Runnable实现类
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都执行了
    }
}
ExcutorService es = Excutors.newFiexThread()

 

   注意:

  1.线程池会一直开启,使用完线程会把线程归还给线程池,线程可以继续使用

  2.如果使用shutdown线程池会被销毁,也就不难继续使用.submit方法。

 

posted @ 2021-03-26 20:35  心岛未晴  阅读(208)  评论(0)    收藏  举报