并发基础(1)---线程池

一、线程池

1、线程池的重要性

  1. 线程池的"池"是解决资源分配的,好处是可以复用线程,也可以控制资源总量

  1. 如果不使用线程池,那么:

  • 一个线程处理

/**
 * 每一个任务开一个线程
 */
public class EveryTaskOneThread {

    public static void main(String[] args) {
        Thread thread = new Thread(new Task(){{run();}});
        thread.start();
    }
    static class Task implements Runnable{

        @Override
        public void run() {
            System.out.println("执行了任务");
        }
    }
}
  • for循环开启线程处理

/**
 * For循环创建线程
 */
public class Forloop {

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            Thread thread = new Thread(new Task(){{run();}});
            thread.start();
        }
    }
    static class Task implements Runnable{

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

我们希望有固定数量的线程,来执行这1000个线程,这样就避免了反复创建销毁线程所带来的开销问题。

2、为什么要使用线程池

问题一:反复创建线程开销大

问题二:过多的线程会占用太多内存

解决以上两个问题的思路

  • 用量的线程—避免内存占用过多

  • 让这部分线程都保特工作,且可以重复执行任务—避免生命周期的损耗

3、线程池的好处

  • 加快响应速度

  • 合理利用CPU和内存

  • 统一管理资源

4、线程池适合应用的场合

  • 服务器接受到大量请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率

  • 实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理

二、创建和停止线程池

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

参数名

类型

含义

corePoolSize

int

核心线程数

maxPoolSize

int

最大线程数

keepAliveTime

Long

保持存活时间

workQueue

BlockingQueue

任务队列

threadFactory

ThreadFactory

线程工厂

handler

RejectedExecutionHandler

拒绝策略

CorePoolSize 核心线程数
  • corePoolSize指的是核心线程数:线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务的到来时,再创建新线程去执行任务,这个时候创建的线程数就等于要去执行任务的数量。当创建新线程完毕后,无论是否有任务,创建的线程会一直存活下来不会减少到核心线程数以下。(除非有异常情况发生)

MaxPoolSize 最大线程数
  • maxPoolSize指的是最大线程数:有些情况下任务越来越多,创建的线程数也应该有个限制,那么核心线程会处理最先到来的任务,新到来的任务会被放到任务队列中(workQueue),这时候任务还没有突破核心线程数+任务队列的总和,核心线程会把任务执行完后,再去任务队列中继续拿任务去执行。当任务越来越多以至于要执行的任务数>核心线程数+任务队列 的时候,于是线程池就会增加线程数到最大线程数去执行任务。

添加任务规则

增加线程的特点
  1. 通过设置corePoolSize和maxPoolSize相同,就能创建固定大小的线程池

  1. 线程池希望保持较少的线程池,并且只有再负载变的很大时才增加它

  1. 通过设置maxPoolSize为很高的值,比如是Integer.Max_VALUE,此线程池能容纳任意数量的并发任务

  1. 只有再队列填满的时候才会创建多于corePoolSize的线程,所以如果使用的是无界队列(LinkedBlockingQueue),那么线程数就不会超过corePoolSize

KeepAliveTime 存活时间

如果线程池当前线程数多于corePoolSize,那么如果多于的线程空闲时间超过keepAliveTime,它们就会被终止。这是一种机制,这种机制是解决线程数过多冗余的时候,造成资源消耗,主要回收的是超过核心线程的线程,但是如过设置参数:***allowCoreThreadTimeOut(true)***核心线程也会被回收

ThreadFactory 线程工厂
  • 新的线程是由ThreadFactory创建的,默认使用Executors.defaultThreadFactory(),创建出来的线程都在同一个线程组,拥有同样的NORM_PRIORITY(5)优先级并且都不是守护线程。如果自己指定ThreadFactory,那么就可以改变线程名、线程组、优先级、是否是守护线程等。

  • 通常我们用默认的ThreadFactory就可以了

WorkQueue 工作队列

有三种最常见的队列类型:

  • 直接交接:SynchronousQueue

这个队列存在的意义是只是对任务做下中转,所以队列是不能存储任务的。在使用这个队列的时候maxPoolSIze要设置的大一点,因为没有队列进行缓冲,会很快的创建线程执行任务。

  • 无界队列:LinkedBlockingQueue

这个队列的队列是不会被填满的,所以maxPoolSize设置多少都是没用的,这种情况下能解决任务数量激增,任务处理不完都存

储再队列中不过这种队列使用可能有风险,如果现场处理任务的速度跟不上队列存储任务的速度的时候,可能会造成OOM。

  • 有界的队列:ArrayBlockingQueue

这个队列可以设置队列大小的,这种情况下现场池中maxPoolSize就有意义了,也是最经常用到的队列

2、线程池应该手动创建还是自动创建

常用自动创建线程池
FixesThreadPool:固定线程池
public class FixedThreadPoolTest {
    public static void main(String[] args) {
        //创建固定线程池,设置线程数为4
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        //循环去执行任务
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }
    static class Task implements Runnable{

        //休息一点时间后,执行任务,任务是打印出当前执行现场的名字
        @Override
        public void run() {
            try {
                Thread.sleep(500);
                System.out.println();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        }
    }
}

最后发现只有四个线程再执行任务

创建原理
//第一个参数corePoolSize和第二个参数maxPoolSize为传进来线程数  证明线程永远是定长
//第三个参数超时为0L就是没有超时时间,线程是不会被回收的
//第五个参数是无界队列,无论任务有多少都会被塞进这个任务队列中去,所以是一直是核心线程再执行任务

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}
特点
  • 由于传进去的LinkedBlockingQueue是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM。

例:
/**
 * 固定线程池OOM练习
 *
 * @author liuzhe
 * @date 2023/02/09
 */
public class FixedThreadPoolOOMTest {
    /**
     * 设置固定线程池
     */
    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());
        }
    }
    static class SubThread implements Runnable{
        //完成任务的时间调整到最大,这样任务就会一直堆集
        @Override
        public void run() {
            try {
                Thread.sleep(1000000000000000000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
SingleThreadExecutor:单线程线程池
public class SingleThreadExecutor {

    public static void main(String[] args) {
        //创建一个单线程线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }

    static class Task implements Runnable{
        //休息一点时间后,执行任务,任务是打印出当前执行线程的名字
        @Override
        public void run() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        }
    }

}
创建原理

//第一个参数corePoolSize和第二个参数maxPoolSize为1  证明线程永远是定长
//第三个参数超时为0L就是没有超时时间,线程是不会被回收的
//第五个参数是无界队列,无论任务有多少都会被塞进这个任务队列中去,所以是一直是核心线程再执行任务
//单线程线程池是属于定长线程线程池,只不过单线程线程池是永远只有一个线程执行任务 
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}

特点
  • 由于传进去的LinkedBlockingQueue是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM。

  • 它只会用唯一的工作线程执行任务

  • 原理和FixedThreadPool一样,但是此时线程数量被设置为了1

CachedThreadPool:可缓存线程池
/**
 * 缓存线程池
 *
 * @author liuzhe
 * @date 2023/02/15
 */
public class CachedThreadPool {


    public static void main(String[] args) {
        //创建可缓存线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }


    static class Task implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
}
创建原理
//第一个参数corePoolSize,核心线程数量是0;第二个参数maxPoolSize是Integer.MAX_VALUE,基本上最大线程数是用不完的,每次执行任务最大线程数都会+1
//第三个参数过期时间为60,第四个参数过期时间单位秒,就是如果有线程超过60s空闲时间,该线程就会被回收
//第五个参数任务队列用的是SynchronousQueue,这个队列是无界的不会存储数据的,只要有一个任务要执行,这个线程池既不会创建核心线程,也不会把该任务存储到任务队列中,而是创建最大线程,由最大线程来执行任务
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

特点
  • 弊端是第二个参数maxPoolSize是Integer.MAX_VALUE,这可能会创建非常多的线程,导致OOM

  • 可以回收线程

ScheduledThreadPool:定时任务线程池
/**
 * 定时线程池
 *
 * @author liuzhe
 * @date 2023/02/15
 */
public class ScheduledThreadPool {

    public static void main(String[] args) {
        //创建定时线程池
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
        //设置延迟定时任务,delay参数的意思是延迟时间多少时间单位后执行该线程
        threadPool.schedule(new Task(),5,TimeUnit.SECONDS);
        //设置间隔定时任务,initialDelay参数的意思是开始多久后执行该线程;period的意思是第一次执行任务后,每隔多久后继续执行任务
        threadPool.scheduleAtFixedRate(new Task(),1,3,TimeUnit.SECONDS);

    }
    static class Task implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }


}
创建原理
//第一个参数corePoolSize为手动输入的
//第二个参数maxPoolSize为Integer.MAX_VALUE,最大线程数几乎是无限的,当任务时间紧急的时候可能因为创建现场过多造成OOM
//第三个参数和第四个参数说明定时线程池不具备自动回收线程功能
//弟五个参数任务队列说明此线程池用的是延迟任务队列
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
特点
  • 定时执行任务

以上4中线程池的构造函数的参数
正确的创建现场池的方法
  • 根据不同的业务场景,自己设置现场池参数,比如内存。

3、线程池里的线程数量设定多少比较合适?

4、停止线程的方法

1.shutdown/isShutdown/isTerminated
代码
/**
 * 演示关闭线程池1
 *
 * @author liuzhe
 * @date 2023/02/16
 */
public class ShunDwonTask1 {

    public static void main(String[] args) {
        //创建一个定长为10的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        try {

            //创建100个任务,用这个线程池去执行
            for (int i = 0; i < 100; i++) {
                executorService.execute(new ShutDownTask());
            }

            //休息1.5s后执行shotdown这个方法
            Thread.sleep(1500);

            //执行showdown方法前,调用isShutdown来判断这个线程是否进入停止状态
            System.out.println("线程池是否进入停止状态:   " + executorService.isShutdown());
            //执行showdown方法前,isTerminated来判断这个线程是否进入停止状态
            System.out.println("线程池是否已经开始停止并且所有任务已经执行完毕:   " + executorService.isTerminated());

            executorService.shutdown();
            //执行showdown方法后,再新增一个任务让线程池去执行,这时线程池就会拒绝添加这个任务不会执行
            executorService.execute(new ShutDownTask());

        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //执行showdown方法后,判断线程是否进入停止状态
            System.out.println("线程池是否进入停止状态:   " + executorService.isShutdown());
            //执行showdown方法后,isTerminated来判断这个是否已经停止并且所有任务已经执行完毕
            System.out.println("线程池是否已经开始停止并且所有任务已经执行完毕:   " + executorService.isTerminated());

            //休息10后,isTerminated来判断这个线程是否已经停止
            try {
                Thread.sleep(10000);
                System.out.println("线程池是否已经开始停止并且所有任务已经执行完毕:   " + executorService.isTerminated());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    static class ShutDownTask implements Runnable{

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

shutdown方法是将线程池设置为停止状态,线程池不会马上关闭,正在执行的任务和在任务队列中存储的任务还是会执行。但是有新任务进来会报异常,

isShutdown方法是判断现场是否处于停止状态,调用shutdown()方法后返回结果为true,此方法只是判断线程池是否处于这个状态,不能判断任务是否执行完毕

isTerminated方法来判断这个是否已经停止并且所有任务已经执行完毕

2.awaitTermination
代码
public class ShunDwonTask2 {

    public static void main(String[] args) {
        //创建一个定长为10的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        try {

            //创建100个任务,用这个线程池去执行
            for (int i = 0; i < 100; i++) {
                executorService.execute(new ShutDownTask());
            }

            //休息1.5s后执行shotdown这个方法
            Thread.sleep(1500);
            executorService.shutdown();


            //注意:awaitTermination方法再返回前是阻塞的,有3种情况有返回值
            //1:所有任务都执行完毕
            //2:所有任务执行前被中断
            //2:到时时间,任务是否已经全部执行完毕

            //3s后,线程池是否已经完全执行完任务
            boolean a = executorService.awaitTermination(3, TimeUnit.SECONDS);
            System.out.println("3s后,线程池是否已经完全执行完任务:  " + a);


            //15s后,线程池是否已经完全执行完任务
            boolean b = executorService.awaitTermination(15, TimeUnit.SECONDS);
            System.out.println("15s后,线程池是否已经完全执行完任务:  " + b);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    static class ShutDownTask implements Runnable{

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


}
特点

awaitTermination方法判断在多次时间后,此线程是否已经停止,并且任务队列中任务全部完成

awaitTermination方法再返回前是阻塞的,有3种情况有返回值

  1. 所有任务都执行完毕

  1. :所有任务执行前被中断

  1. 到时时间,任务是否已经全部执行完毕

这三种情况返回true,否则返回fasle

3.shutdownNow
代码
public class ShunDwonTask3 {

    public static void main(String[] args) {
        //创建一个定长为10的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        try {

            //创建100个任务,用这个线程池去执行
            for (int i = 0; i < 100; i++) {
                executorService.execute(new ShutDownTask());
            }

            //休息1.5s后执行shutdownNow这个方法
            Thread.sleep(1500);
            //正在执行任务的线程会立即中断,并且每个线程都抛出异常 sleep interrupted
            //队列中没有执行的线程会变成返回值返回
            List<Runnable> runnables = executorService.shutdownNow();


        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    static 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() + "被中断了");
            }
        }
    }
}
特点

shutdownNow方法会将正在执行任务的线程会立即中断,并且每个线程都抛出异常 sleep interrupted、队列中没有执行的线程会变成返回值返回

5、任务太多,怎么拒绝

拒绝时机
  1. 当Executor关闭时,提交新任务会被拒绝(例如:调用shutdown或者shutdownNow方法后,又开始调用此线程池的execute方法)

  1. 以及当Executor对最大线程和工作队列容量使用有限边界并已经饱和时

拒绝策略
  • AbortPolicy

直接抛出异常,并且拒绝执行

  • DiscardPolicy

不抛出异常,但是会把任务丢弃,不知道新提交任务是否执行

  • DiscardOldestPolicy

抛弃出等待时间最长没有执行的任务,不知道新提交任务是否执行

  • CallerRunsPolicy

谁提交的这个任务,谁来执行这个线程(主线程)去执行这个任务。任务执行可以避免业务损失,可以给线程池一定缓冲时间

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值