Day289.线程池 -Juc

Posted 阿昌喜欢吃黄桃

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day289.线程池 -Juc相关的知识,希望对你有一定的参考价值。

线程池

一、线程池的自我介绍

1、线程池的重要性

  • 复用每一个线程
  • 控制资源总量,便于管理

2、什么是 “池”

  • 软件中的“池”,可以理解为计划经济

3、不使用线程池会怎么样

  • 每次创建和销毁线程都需要资源的开销,会有很大部分的资源浪费

  • 一个线程

public class EveryTaskOneThread {
    public static void main(String[] args) {
        Thread thread = new Thread(new Task());

        thread.start();

    }
    static class Task implements Runnable{
        @Override
        public void run() {
            System.out.println("执行了任务");
        }
    }
}
  • for循环创建10个线程执行任务
public class ForLoop {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Task());
            thread.start();
        }
    }

    static class Task implements Runnable{
        @Override
        public void run() {
            System.out.println("执行了任务");
        }
    }
}

image-20210605171859682

  • 当任务数量上升到1000个数量级

*这种for循环1000的情况,java语言中的线程会直接对应操作系统中的1000个线程,也就是说在操作系统中创建了1000个线程,带来巨大的开销===>内存、垃圾回收器等…

image-20210605171946684


4、为什么需要使用线程池

  • 反复创建线程开销大

  • 过多的线程会导致内存消耗


5、线程池的好处

  • 加快响应速度
  • 便于线程的管理控制,方便数据统计
  • 合理利用CPU和内存

6、线程池适合的场合

image-20210605172645438

二、创建和停止线程池

1、线程池构造函数的参数

image-20210605181514725

  • 参数:corePoolSize

线程池初始化后,默认是没有任何线程的,线程池会等待有任务到来时,再去创建新线程去执行任务

核心的数量就是保持活跃的线程数量

  • 参数:maxPoolSize

线程创建上限

image-20210605182300227


  • 增加线程规则

image-20210605182720865


  • 流程图

image-20210605182808602


  • 是否需要增加线程的判断
    • corePoolSzie
    • workQueue
    • maxPoolSize

  • 举一个例子

image-20210605183024909


  • 增减线程的特点

    • 通过设定corePoolSize & maxPoolSize相同,可以创建固定大小的线程池

    • 线程池希望保持较少的线程数量,并且只有在负载变得很大时才去增加它

    • 通过设定maxPoolSize的值很高,如Integer.MAX_VALUE,可以创建出一个容纳任意数量的并发任务

    • 如果设置一个无上限的队列(如LinkedBlockingQueue),那么线程数就不会超过corePoolSize


  • 参数:keepAliveTime:

如果线程池当前的线程数多于corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime,多次来超时的就会被回收终止


  • 参数:ThreadFactory

用来创建线程

image-20210605184151127

image-20210605184610777


  • 参数:workQueue

image-20210605185014449


2、线程池选择 [手动创建]&[自动创建]?

手动创建线程池更好,可以明确线程池运行规则避免资源耗尽风险

①newFixedThreadPool

  • 源码

image-20210605185656840


  • 代码演示
/******
 @author 阿昌
 @create 2021-06-05 18:53
  *******
  *          演示newFixedThreadPool
 */
public class FixedThreadPoolTest {
    //主函数
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        //执行线程池
        for (int i = 0; i < 1000; i++) {
            threadPool.execute(new Task());
        }
    }

    //任务类
    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(500);
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

↓↓↓,发现只有5个线程在执行任务,跟我们设定5个线程执行要求是一致的

image-20210605190021791


  • 演示错误

当因为任务过多无法处理导致内存资源耗尽*

/******
 @author 阿昌
 @create 2021-06-05 19:03
 *******
 *      演示newFixedThreadPool出错的情况
 */
public class FixedThreadPoolOOM {
    //线程池
    private static ExecutorService executorService = Executors.newFixedThreadPool(1);

    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            //执行任务
            executorService.execute(new SubThread());
        }
    }
}

//任务类
class SubThread implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(1000000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

一直执行,观察任务管理器,发现内存直接大量增加

image-20210605190936718

image-20210605191025173

发现太慢了,我们就直接主动设置jvm参数让他快点出现内存溢出的情况

-Xmx8m -Xms8m

image-20210605191233116

最后出现了内存溢出

image-20210605191143036

image-20210605191346237


②newSingleThreadExecutor

  • 代码演示
public class SingleThreadExecutor {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }

    }
}
 class Task implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

image-20210605191948388

  • 源码

image-20210605192118191

image-20210605192156941


③newCachedThreadPool

可缓存线程池

  • 特点:采用SynchronousQueue无界限线程池,具有60s自动回收多余线程的功能

  • 源码

image-20210605192453019

  • 代码演示
public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();

        for (int i = 0; i < 1000; i++) {
            pool.execute(new Task());
        }
    }

    static class Task implements Runnable{
        @Override
        public void run() {
            try {
                Thread.sleep(500);
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

每一个任务,他就创建一个线程,↓↓↓他创建了1000个线程

image-20210605192807320

image-20210605193121480


④newScheduledThreadPool

  • 支持定时及周期性任务执行的线程池

  • 代码演示

public class ScheduleThreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);

        //5秒钟执行任务,不重复执行
//        pool.schedule(new Task(),5, TimeUnit.SECONDS);

        //一开始1后执行任务,后面每隔3秒执行
        pool.scheduleAtFixedRate(new Task(),1,3,TimeUnit.SECONDS);
    }
}

image-20210605193751945


3、线程池设定线程数量如何选择?

image-20210605194049498


4、停止线程的正确方法

  • shutdown

  • isShutdown

  • isTerminated

  • awaitTermnation

  • shutdownNow

代码演示

//演示关闭线程池
public class ShutdownPool {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 100; i++) {
            pool.execute(new ShutdownTask());
        }

        Thread.sleep(1500);

        pool.shutdownNow();

//        boolean flag = pool.awaitTermination(3, TimeUnit.SECONDS);
//        System.out.println(flag);
//        System.out.println(pool.isShutdown());
        //关闭线程池
//        pool.shutdown();
//        System.out.println(pool.isShutdown());
//        Thread.sleep(10000);
//        System.out.println(pool.isTerminated());
        //再次执行任务
//        pool.execute(new ShutdownTask());
    }
}

class ShutdownTask implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+": 被中断了");
        }
    }
}

三、常见线程池的特点和用法

image-20210605194421939

image-20210605194747815


四、任务太多,怎么拒绝?

1、拒绝时机

image-20210605202038895


2、拒绝策略

image-20210605202358782


3、钩子方法

//演示任务前后,可有钩子桉树
public class PauseableThreadPool extends ThreadPoolExecutor {
    private boolean isPaused;//标记位
    private final ReentrantLock lock = new ReentrantLock();//锁
    private final Condition unpaused = lock.newCondition();//类似wait/notify...

    //主函数
    public static void main(String[] args) throws InterruptedException {
        PauseableThreadPool pool = new PauseableThreadPool(10, 20, 10L, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println("我被执行了");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        for (int i = 0; i < 10000; i++) {
            pool.execute(task);
        }

        Thread.sleep(1500);

        pool.pause();
        System.out.println("线程池被暂停了。。。。。。。。。。");

        Thread.sleep(1500);

        pool.resume();
        System.out.println("线程池恢复了。。。。。。。。。。。。");

    }

    //钩子函数,执行前
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        lock.lock();
        try {
            while (isPaused) {
                //暂停线程
                unpaused.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //暂停
    private void pause() {
        lock.lock();
        try {
            isPaused = true;
        } finally {
            lock.unlock();
        }
    }

    //恢复
    private void resume() {
        lock.lock();
        try {
            isPaused = false;
            //唤醒线程
            unpaused.signalAll();
        } finally {
            lock.unlock();
        }
    }
    
    //下面↓↓↓ 是继承 ThreadPoolExecutor 默认实现的
    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }
}

五、线程池实现原理

1、线程池组成部分

  • 线程池管理器

  • 工作线程

  • 任务队列

  • 任务接口(Task)


2、Executor家族

  • 哪个是线程池

image-20210605211457106


  • Executor

image-20210605211751835

  • ExecutorService

  • Executors: 工具类


3、线程池实现任务复用

在start()里面执行各个任务的run()方法

在while中不能的循环执行

image-20210605212713894

image-20210605212725020


六、线程池状态

image-20210605212926465

image-20210605213139629


  • execute()方法的源码

image-20210605214733655


七、使用线程池的注意点

image-20210605213641858

以上是关于Day289.线程池 -Juc的主要内容,如果未能解决你的问题,请参考以下文章

day19-线程之间的通信&线程池&设计模式

day19-线程之间的通信&线程池&设计模式

重修课程day34(网络编程八之线程二)

Day793.合理设置线程池大小 -Java 性能调优实战

java并发 day05 ThreadPoolExecutorJUC

java并发 day05 ThreadPoolExecutorJUC