关于线程的学习3

本文详细介绍了Java中的线程等待和通知机制,包括wait()、wait(long)、notify()和notifyAll()的使用,并通过同步方法和同步代码块展示了其实现。此外,还探讨了wait()与sleep()的区别以及生产者消费者模式的应用,以缓冲区和阻塞队列为例解释了解决并发问题的方法。最后,讨论了线程池的概念和优化配置,强调了其在资源管理中的重要性。
摘要由CSDN通过智能技术生成

线程的等待和通知

  • wait() 让当前线程进入等待状态,直到被通知为止

  • wait(long) 让当前线程进入等待状态,同时设置时间;直到被通知为止或时间结束

  • notify() 随机通知一个等待线程

  • notifyAll() 通知所有的等待线程

等待再通知的实现

1.同步方法实现

public class WaitDemo {
    public synchronized void print(){
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
            if (i==50) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

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

2.同步代码块实现

public class WaitDemo2 {
    public void print(){
        synchronized (this) {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "---" + i);
                if (i == 50) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public void notifyTest(){
        synchronized (this) {
            this.notify();
        }
    }
    public static void main(String[] args) {
        WaitDemo2 waitDemo = new WaitDemo2();
        new Thread(()->{
            waitDemo.print();
        }).start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        waitDemo.notifyTest();
    }
}

3.静态实现

public class WaitDemo3 {
    public static void print(){
        synchronized (WaitDemo3.class) {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "---" + i);
                if (i == 50) {
                    try {
                        WaitDemo3.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void notifyTest(){
        synchronized (WaitDemo3.class) {
            WaitDemo3.class.notify();
        }
    }
    public static void main(String[] args) {
        new Thread(()->{
           print();
        }).start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        notifyTest();
    }
}

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

重点 :wait()和sleep()的区别

  1. 调用对象不同

    wait() 由锁对象调用,sleep() 由线程调用

  2. 锁使用不同

    执行wait后,自动释放锁,执行sleep后,不会释放锁

  3. 唤醒机制不同

    执行wait后,可以被通知唤醒,执行sleep后,只能等待时间结束后,自动唤醒

 生产者消费者模式

设计模式,不属于GOF23

  • 生产者

    某些程序/进程/线程负责生产数据就属于生产者,类似于地球中的植物,负责生产能量

  • 消费者

    某些程序/进程/线程负责使用数据就属于消费者,类似于地球中的动物,负责消耗掉能量

 生产者与消费者之间存在的问题

耦合性高:生产者和消费者之间的联系过于精密,不适合代码的维护,一旦修改要改的地方过多

并发性能低:同时能处理的的请求量少

忙闲不均:有时会出现生产者多余消费者,会出现多余情况,有时会消费者过多,会出现不够的情况,会给系统资源带来浪费

解决方法:

  1. 通过添加缓冲区,设置上限

  2. 生产者生产数据,向缓冲区存放,如果满了,生产者进入等待,直到缓冲区有空的位置通知生产者生产;

  3. 消费者从缓冲区取数据进行消费,如果空了,消费者进入等待,直到缓冲区有数据再通知消费者消费。

 

public class BaoziShop {
    /**
     * 包子
     */
    class Baozi{
        private int id;
        public Baozi(int id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return "包子--" + id;
        }
    }
    //上限
    public static final int MAX_COUNT = 100;
    //缓冲区 存放数据
    private List<Baozi> baozis = new ArrayList<>();

    /**
     * 做包子
     */
    public synchronized void makeBaozi() throws InterruptedException {
        //判断缓冲区是否满了
        if(baozis.size() == MAX_COUNT){
            System.out.printf("缓冲区满了,%s等待%n",Thread.currentThread().getName());
            //让生产者线程等待
            this.wait();
        }else{
            //通知生产者线程生产
            this.notifyAll();
        }
        //创建包子
        Baozi baozi = new Baozi(baozis.size() + 1);
        System.out.println(Thread.currentThread().getName()+"做了"+baozi);
        //保存到缓冲区
        baozis.add(baozi);
    }

    /**
     * 拿包子
     */
    public synchronized void takeBaozi() throws InterruptedException {
        //判断缓冲区是否空了
        if(baozis.size() == 0){
            System.out.printf("缓冲区空了,%s等待%n", Thread.currentThread().getName());
            //让消费者等待
            this.wait();
        }else{
            //通知消费者消费
            this.notifyAll();
        }
        //获得第一个包子,并删除
        if(baozis.size() > 0){
            Baozi baozi = baozis.remove(0);
            System.out.println(Thread.currentThread().getName()+"吃了"+baozi);
        }
    }

    public static void main(String[] args) {
        BaoziShop baoziShop = new BaoziShop();
        //一个生产者
        Thread productor = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                try {
                    baoziShop.makeBaozi();
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        productor.start();
        //10个消费者吃10个包子
        for (int i = 0; i < 10; i++) {
            Thread consumer = new Thread(() ->{
                try {
                    for (int j = 0; j < 10; j++) {
                        baoziShop.takeBaozi();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            consumer.start();
        }
    }
}

阻塞队列

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

BlockingQueue 接口

  • put 添加数据,达到上限会自动让线程等待

  • take 取并删除数据,数据空了会自动让线程等待

实现类

ArrayBlockingQueue 类 数据结构为数组 ,适合于多读少改的情况

LinkedBlockingQueue类 链表结构,适合于少读多改的情况

public class BaoZiShop {

    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 baozi = baozis.take();
                        System.out.println(Thread.currentThread().getName()+"消费了"+baozi);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

 线程池

作用:回收利用线程资源

线程是一种非常宝贵的资源,执行完任务后就会死亡,而有时候有大量任务需要执行,频繁的生成和销毁线程会对资源产生压力,造成巨大的浪费,造成系统性能的降低,线程池可以保存一定量的线程,将他们进行回收利用,这样可以对系统资源进行更高效的利用,线程执行完任务后,会回到线程池中,等待下一个任务,节省系统资源,提升性能。

线程池使用

顶层为一个接口:Executor

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

ExecuterService,继承了Executer,添加了线程管理的方法如 :shutdown()、shutdownNow()

Executors:用于创建线程池的工具类

主要方法

方法名作用        
newCachedThreadPool()   创建长度不限的线程池
newFixedThreadPool(int )创建固定长度的线程池
newSingleThreadExecutor()创建单一个数的线程池
newScheduledThreadPool(int)创建可以调度的线程池

线程池的优化配置

线程池的实现类   

ThreadPoolExecutor

线程池的构造方法参数:

  • corePoolSize 核心线程数,创建线程池后自带线程,不会进行销毁

  • maximumPoolSize 最大线程数

  • keepAliveTime 存活时间,非核心线程能够闲置的时间,超过后被销毁

  • timeUnit 时间单位

  • blockingQueue 阻塞队列 存放任务(Runnable)的集合

优化   

  1. 核心线程数 应该和CPU内核数量相关 CPU内核数 * N (N和任务执行需要时间和并发量相关)

  2. 最大线程数可以和核心线程数一样,避免频繁创建和销毁线程

  3. 如果存在非核心线程,设置大一点,避免频繁创建和销毁线程

  4. 阻塞队列使用LinkedBlockingQueue,插入和删除任务效率更高

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值