线程池(阻塞队列,生产者消费者模式)

2023-05-16

线程的等待和通知

Object类中有些方法不可随便调用
wait()、notify()、notifyAll()这三个方法:
1.wait() 使线程主动释放锁,进入等待状态,直到他被其他的线程通过 notify() 和 notifyAll() 唤醒 或者 超过等待时间
2.wait(long) 让当前线程进入等待状态,同时设置时间;直到被通知为止或时间结束
3.notify() 随机通知一个等待线程
4.notifyAll() 通知所有的等待线程

注意:等待和通知方法必须是锁对象,否则会抛出IllegalMonitorStateException

/**
* 通过锁对象将线程等待,经过5秒通知该线程来执行
*/
public class WaitDemo {

    public synchronized void waiteTest() throws InterruptedException {
        //当前方法等待
        System.out.println(Thread.currentThread().getName() + "当前方法等待5s");
        this.wait();
    }

    public synchronized void notifyTest(){
        System.out.println("唤醒等待中的方法");
        this.notify();
    }

    public static void main(String[] args) {
        WaitDemo waitDemo = new WaitDemo();
        Thread thread = new Thread(() -> {
            try {
                waitDemo.waiteTest();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        try {
            Thread.sleep(5000);
            waitDemo.notifyTest();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

wait()和sleep()区别

  1. 调用对象不同

    wait() 由锁对象调用

    sleep() 由线程调用

  2. 锁使用不同

    执行wait后,自动释放锁

    执行sleep后,不会释放锁

  3. 唤醒机制不同

    执行wait后,可以被通知唤醒

    执行sleep后,只能等待时间结束后,自动唤醒

生产者消费者模式

例子:假如一个包子铺卖包子,应该是一边生产,一边销售,工作效率最高。如果来了一群顾客,这时候开始一个个生产效率太低,所以顾客还没来就要开始生产,这就需要一个蒸笼,把生产好的包子放在蒸笼里,顾客来了就卖给顾客。

生产者:生产数据的线程或者进程
消费者:使用数据的线程或进程

而蒸笼就相当于一个缓存区
模式实现思路:
1.添加缓冲区,设置上限
2.向缓存区存放数据,满了使生产者进入等待,有空位置则生产
3.消费者从缓冲区使用数据,缓存区没有数据则消费者等待,有数据再通知消费者

解决问题

  • 为生产和消费解耦
  • 提高并发性能
  • 解决忙闲不均
public class BaoziDemo0 {
    public static void main(String[] args) {
        BaoziShop baoziShop = new BaoziShop();
        //创建一个线程,用来生产
        for (int i = 0; i < 150; i++) {
            new Thread(()->{
                try {
                    baoziShop.produceBao(baoziShop.baozis.size() + 1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        //创建一个线程,用来消费
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    for (int j = 0; j < 20; j++) {
                        baoziShop.sellBao();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

//包子铺
class BaoziShop{

    //缓冲区 存放数据
    List<BaoZi> baozis = new ArrayList<>();

    //上限
    int MAX_COUNT = 100;

    //生产包子
    public synchronized void produceBao(int id) throws InterruptedException {
        //如果缓存区满了
        if (baozis.size() == MAX_COUNT){
            System.out.println("缓存区满了---------------------" + Thread.currentThread().getName()+"等待");
            this.wait();
        }else {
            //通知卖包子线程
            this.notify();
        }
        BaoZi baoZi = new BaoZi(baozis.size() + 1);
        baozis.add(baoZi);
        System.out.println( Thread.currentThread().getName() + "师傅生产一个包子" + baoZi);
    }

    //卖包子
    public synchronized void sellBao() throws InterruptedException {
        //如果缓存区没包子了
        if (baozis.size() == 0){
            System.out.println("缓存区空了--------------" +Thread.currentThread().getName()+"等待");
            this.wait();
        }else {
        //否则,通知生产包子
            this.notify();
        }
        if (baozis.size()>0){
            BaoZi baoZi = baozis.remove(0);
            System.out.println("顾客买了一个包子" + baoZi);
        }
    }
}

//包子类
class BaoZi{
    public Integer id;

    public BaoZi(Integer id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "BaoZi-"+ id;
    }
}

阻塞队列

应用了生产者消费者模式的集合,能够根据数据满或空的情况,自动对线程执行等待和通知

BlockingQueue接口

  • put 添加数据,达到线程上限会让线程自动等待
  • take 取并删除数据,数据空了会让线程自动等待
    实现类
    ArrayBlockingQueue类 数组
    LinkedBlockingQueue类 链表结构
public class BaoziDemo {
    /**
     * 包子
     */
    static class Baozi{
        private int id;
        public Baozi(int id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return "包子--" + id;
        }
    }

    public static void main(String[] args) {
        //阻塞队列
        BlockingQueue<Baozi> baozis = new ArrayBlockingQueue<>(100);
        //生产者线程
        new Thread(() -> {
            for (int i = 0; i < 200; i++) {
                //创建包子,添加到阻塞队列,满了就自动阻塞线程
                Baozi baozi = new Baozi(baozis.size() + 1);
                try {
                    baozis.put(baozi);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"生产了" + baozi);
            }
        }).start();
        //消费者线程
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                for (int j = 0; j < 40; j++) {
                    //取包子取完了会自动阻塞
                    try {
                        Baozi take = baozis.take();
                        System.out.println(Thread.currentThread().getName() + "消费了" + take);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

线程池

我们创建线程在运行结束后都会被虚拟机销毁,如果线程数量多的话,频繁的创建和销毁线程会大大浪费时间和效率,更重要的是浪费内存。那么有没有一种方法能让线程运行完后不立即销毁,而是让线程重复使用,继续执行其他的任务?

顶层接口:Executor

  • execute(Runnable) 启动线程执行一个任务

ExecutorService 继承 Executor 添加了线程池管理方法,如:shutdown()、shutdownNow()

ThreadPoolExecuter作为线程池的实现类 继承了实现 ExecutorService接口的抽象类AbstractExecutorService

  • corePoolSize 核心线程数,创建线程池后自带线程,不会进行销毁
  • maximumPoolSize 最大线程数
  • keepAliveTime 存活时间,非核心线程能够闲置的时间,超过后被销毁
  • timeUnit 时间单位
  • blockingQueue 阻塞队列 存放任务(Runnable)的集合

优化配置

  1. 核心线程数 应该和CPU内核数量相关 CPU内核数 * N (N和任务执行需要时间和并发量相关)
  2. 最大线程数可以和核心线程数一样,避免频繁创建和销毁线程
  3. 如果存在非核心线程,设置大一点,避免频繁创建和销毁线程
  4. 阻塞队列使用LinkedBlockingQueue,插入和删除任务效率更高
public class ThreadPoolExecutorDemo {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService threadPoolExecutor =
                new ThreadPoolExecutor(2, 5,
                        5000L, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>() {
                });
        for (int i = 0;i<50; i++) {
            int n = i+1;
            threadPoolExecutor.execute(()->{
                System.out.println(Thread.currentThread().getName() + "执行了任务" + n);
            });
        }
    }
}

线程池原理
预先启动一些线程,线程无限循环从任务队列中获取一个任务进行执行,直到线程池被关闭。如果某个线程因为执行某个任务发生异常而终止,那么重新创建一个新的线程而已,如此反复。
在这里插入图片描述
工作线程的集合都在一个存储work对象的HashSet中,线程池的工作线程通过Woker类实现,通过ReentrantLock锁保证线程安全。添加线程到workers中(线程池中)。 多余的任务会放在阻塞队列中。只有当阻塞队列满了后,才会触发非核心线程的创建。所以非核心线程只是临时过来打杂的。直到空闲了,然后自己关闭了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

线程池(阻塞队列,生产者消费者模式) 的相关文章

随机推荐

  • 快速排序详解-java实现

    一 快速排序 整体过程 xff1a 1 先从数组中找一个数作为基准数 xff0c 2 进行分区 xff0c 分区时大于这个数得全部放到右边 xff0c 小于这个数得全部放到左边 xff0c 等于这个数得全部放到中间 xff08 核心过程 x
  • 应届生应该如何准备校招

    大家好 xff0c 我是羽峰 xff0c 今天要和大家分享的是应届生应该如何准备校招 xff0c 希望该文章对一些今年要找工作的朋友有一些帮助 还是老话 xff0c 我是羽峰 xff0c 希望我所分享的文章能为您及更多的朋友带来帮助 欢迎转
  • mysql提示Can‘t connect to MySQL server on localhost (10061)解决方法

    本文mysql的安装环境为win10 64位 xff0c mysql版本为MySQL5 7 我遇到的问题是 电脑原先安装过mysql xff0c 卸载不干净 xff0c 重装默认以前的用户名和密码 xff0c 然鹅 我不知道用户名和密码 以
  • 哈希表与一致性哈希表

    哈希表定义 散列表 xff08 Hash table xff0c 也叫哈希表 xff09 xff0c 是根据关键码值 Key value 而直接进行访问的数据结构 它通过把关键码映射到表中一个位置来访问记录 xff0c 以加快查找的速度 这
  • 【AI欣赏】将火影忍者推向新高度:StableDiffusion的神奇效果

    AI欣赏 将火影忍者推向新高度 xff1a StableDiffusion的神奇效果 x1f525 你是否曾经梦想过将自己的绘画水平提升到一个新的高度 xff0c 让你的艺术作品能够轻松地与官方制作相媲美呢 xff1f StableDiff
  • 2023-03-06 debian11 最小安装记录

    1 镜像准备 xff0c 根据个人需求下载debian 版本 Debian 获取 Debian 2 上传到VSAN 内容库 我这边是在vm里面安装的 xff0c 就直接上传到内容库备用 xff08 根据个人需求存放 xff09 3 分配虚拟
  • Vue 项目启动报错:http://eslint.org/docs/rules/no-unused-vars

    Vue项目启动时报 xff1a http eslint org docs rules no unused vars 39 vm 39 is assigned a value but never used src navigation nav
  • Android解决相机预览拉伸问题

    拉伸的原因 1 主要是由于surfaceview的尺寸和 相机预览尺寸不一致造成的 64 Override span class token keyword public span span class token keyword void
  • Linux技巧

    0001 修改主机名 bjchenxu vi etc sysconfig network xff0c 修改HOSTNAME一行为 34 HOSTNAME 61 主机名 34 没有这行 xff1f 那就添加这一行吧 xff0c 然后运行命令
  • 8.消抖技术

    简介 开关的金属触点在断开闭合的过程中产生了多个信号 xff0c 消抖就是保证只有一个信号起到作用 这样的小抖动不像快脉冲一样造成市电上的其他设备出现故障 但是会导致模拟电路快速翻转的数字信号电路出现故障 便宜的电脑键盘通常使用导电橡胶制成
  • 1.7-1.8动态库加载失败的原因和解决办法

    目录 1 动态库和静态库的原理 2 动态库加载失败的原因 3 解决方法 3 1 DT RPATH段 3 2将libcalc so的路径加入到环境变量LD LIBRARY PATH中 3 3将libcalc so的路径加入到 etc ld s
  • Spring中Bean创建完成后执行指定代码的几种实现方式

    Spring中Bean创建完成后执行指定代码的几种实现方式 1 实现ApplicationListener接口2 实现InitializingBean接口3 使用 64 PostConstruct注解 在实际开发中经常会遇到在spring容
  • archlinux fcitx5 在浏览器中不能使用中文输入法

    archlinux系统升级后 xff0c fcitx5 在浏览器中不能使用中文输入法 xff0c 解决办法 xff1a 编辑 etc environment 并添加以下几行 xff0c 然后重新登录 1 xff1a GTK IM MODUL
  • ANR问题了解新思路

    众所周知 xff0c ANR问题一般不太容易解决 它是一种比较综合性的问题 往往涉及系统事件分发逻辑 xff0c ANR产生机制 xff0c BINDER机制 xff0c 线程同步 xff0c CPU和内存使用等各方面 意思就是说 xff0
  • 2021-01-14

    Design expert教程 单因素实验设计 Part 1 基础简介在这个教程中 xff0c 我们会使用Design Expert创建一个常用的单因素多水平设计 这种类型的设计对分类处理的简单比较非常有用 xff0c 例如 xff1a 谁
  • Statement和PreparedStatement的区别/PreparedStatement和Statement比较的优点

    Statement 和 PreparedStatement之间的关系和区别 关系 xff1a PreparedStatement继承自Statement 都是接口 区别 xff1a PreparedStatement可以使用占位符 xff0
  • MATLAB自适应中值滤波

    span class token operator span 自适应中值滤波 clc span class token punctuation span clear span class token punctuation span clo
  • 操作系统经典问题之生产者消费者问题

    一 生产者消费者问题 生产者和消费者问题是计算机同步互斥的经典问题 xff0c 其意思就是生产者把生产出来的产品放在仓库里 xff0c 消费者把产品从仓库里取出来 仓库属于临界区 xff0c 生产者和消费者一次只能一个进入临界区中 两个进程
  • ctags使用教程

    Ctags ctags在http ctags sourceforge net 下载源码 xff0c 编译后安装 常规的标记命令为 ctags R 34 R 34 表示递归创建 xff0c 也就包括源代码根目录下的所有子目录下的源程序 绝大多
  • 线程池(阻塞队列,生产者消费者模式)

    线程的等待和通知 Object类中有些方法不可随便调用 wait notify notifyAll 这三个方法 xff1a 1 wait 使线程主动释放锁 xff0c 进入等待状态 xff0c 直到他被其他的线程通过 notify 和 no