【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

2年前 (2022) 程序员胖胖胖虎阿
246 0 0

线程池

    • 一、线程存在的问题
    • 二、什么是线程池?
    • 三、线程池的优点
    • 四、线程池的使用
      • 4.1 固定数量的线程池
        • 4.1.1 具体实现
        • 4.1.2 线程工厂
      • 4.3 带缓存的线程池
      • 4.4 执行定时任务的线程池
        • 4.4.1 延迟执行(一次)
        • 4.4.2 固定频率执行
          • 4.4.2.1 scheduleAtFixedRate
          • 4.4.2.2 scheduleWithFixedDelay
          • 4.4.2.3 scheduleAtFixedRate VS scheduleWithFixedDelay
      • 4.5 单线程的线程池
      • 4.6 定时任务的单线程线程池
      • 4.7 根据当前CPU生成线程池
      • 4.8 ThreadPoolExecutor的使用
        • 4.8.1 Executors自动创建线程池可能存在的问题
        • 4.8.2 ThreadPoolExecutor使用
        • 4.8.3 线程池的执行流程
        • 4.8.4 拒绝策略
    • 五、线程池的状态
      • 5.1 Shutdown VS ShutdownNow

一、线程存在的问题

之前我们已经学习过了线程,线程的创建方式我们讲了7种,感兴趣的小伙伴可以去看看前面的讲解。线程的创建

那么我们学习了那么多关于线程的知识,为什么还要再用到线程池呢?那么我们就要来思考一下,线程有什么问题吗?

  • 首先,每次有任务来时就要创建线程,任务结束就要将这个线程销毁,频繁的创建和销毁需要一定的开销。
  • 当任务数远远大于线程可以承载的数量之后,不能友好的进行任务拒绝。

因此引出了线程池~~

二、什么是线程池?

线程池是使用池化技术管理和使用线程的一种机制。

池化技术:提前准备一些资源,在需要时可以重复使用使用提前准备的资源。
池化技术的应用也比较多:

  • 内存池:预先申请内存,在 使用时提升申请内存的速度,减少内存碎片。
  • 数据库连接池:预先申请数据库连接,提升申请连接的速度,降低系统开销。

三、线程池的优点

线程池相比线程来说,它不需要频繁的创建和销毁线程,而是提前将线程创建好休眠在线程池中,有任务需要它时再唤醒它,线程一旦创建就不会销毁,会放入线程池中,如下图:
【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了
因此,他有以下几个优点:

  1. 复用线程,避免线程重复创建和销毁线程的性能开销。
  2. 控制线程的数量 ,从而避免了因创建线程过多而导致的内存溢出(OOM).
  3. 提供了任务管理的功能。从而可以实现任务缓存和任务拒绝的情况。
  4. 线程池提供了更多功能,比如延时任务。

四、线程池的使用

线程池的实现总的来说有两类:

  • 通过ThreadPoolExecutor创建的线程池;
  • 通过Executors创建的线程池。

线程池的创建方式总共包含以下7种(其中6种是通过Executors 创建的,1种是通过ThreadPoolExecutor创建的):

  • Executors.newFixedThreadPool:创建一个固定大小的线程池,可以控制并发的线程数,超出的线程会在队列中等待;
  • Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
  • Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
  • Executors.newSingleThreadExecutor:创建单个线程的线程池,它可以保证先进先出的执行顺序
  • Executors.newSingleThreadScheduledExecutor:创建一个可以执行延迟任务的单个线程的线程池;
  • Executors.newWorkStealingPool:创建一个抢占式执行的线程池;
  • ThreadPoolExecutor:手动创建线程池 的方式,它最多包含了7个参数可供设置,最少可设置5个参数。

4.1 固定数量的线程池

4.1.1 具体实现

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 创建一个固定大小的线程池
 */
public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        //1.创建了一个包含了5个线程的线程池
        ExecutorService threadPool= Executors.newFixedThreadPool(5);

        //2,使用线程池执行任务二(给线程池添加任务)
        for (int i = 0; i < 10; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名称:"+Thread.currentThread().getName());
                }
            });
        }
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

public class ThreadPoolDemo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1.创建了一个包含了5个线程的线程池
        ExecutorService threadPool= Executors.newFixedThreadPool(5);
        //2,使用线程池执行任务(给线程池添加任务)
        for (int i = 0; i < 5; i++) {
            Future<Integer> result = threadPool.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int num=new Random().nextInt(9);
                    System.out.println("随机数:"+num);
                    return num;
                }
            });
            System.out.println("线程池的返回结果:"+result.get());
        }
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

观察上面这两段代码,我们可以发现,向线程池中添加任务的方式有两种:

  • execute:只能执行不带返回值的任务;
  • submit:它可以执行有返回值的任务或者是没有返回值的任务 。
    【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了
    【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

4.1.2 线程工厂

在上面的执行中,可以看到没有设置线程的名称时,它就会是一个默认名 ,线程工厂可以让我们自定义线程名称或优先级
下面来看一下线程工厂的实现吧~

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
 * 线程工厂的演示
 */
public class ThreadPoolDemo3 {
    public static void main(String[] args) {
       //1.创建线程工厂
        ThreadFactory threadFactory=new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread=new Thread(r);
                //设置线程命名规则
                thread.setName("我的线程-"+r.hashCode());
                //设置线程优先级
                thread.setPriority(Thread.MAX_PRIORITY);
                return thread;
            }
        };
        ExecutorService threadPool = Executors.newFixedThreadPool(5, threadFactory);
        for(int i=0;i<5;i++){
            threadPool.submit(()->{
                //任务
                Thread thread = Thread.currentThread();
                System.out.println("线程池开始执行了:"+thread.getName()+" "+thread.getPriority());
            });
        }
    }
}

在上面的代码中我们设置了线程的名称和优先级,看看他会生效吗?
【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了
答案是肯定的,但是要注意的是我们一定要将任务传入这个线程工厂,否则就会出错。【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了
如果我们什么都不传入,就会是什么都不打印。
【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

4.3 带缓存的线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 带缓存的线程池
 */
public class ThreadPoolDemo4 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newCachedThreadPool();
        for(int i=0; i<1000; i++){
            int finalI=i;
            threadPool.submit(()->{
                System.out.println("i:"+finalI+"|线程名称:"+Thread.currentThread().getName());
            });
        }
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了
有1000个任务并不会创建1000个线程,通过执行可以发现最多会创建100多个线程,然后进行复用。

4.4 执行定时任务的线程池

4.4.1 延迟执行(一次)

import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 创建执行定时任务的线程池
 */
public class ThreadPoolDemo5 {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("添加任务的时间:"+ LocalDateTime.now());
        scheduleTest(service);//只执行一次的定时任务

    /**
     * 只执行一次的定时任务
     * @param service
     */
    private static void scheduleTest(ScheduledExecutorService service) {
        //执行定时任务(延迟3秒执行)
        service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行了任务:"+LocalDateTime.now());
            }
        },3, TimeUnit.SECONDS);
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了
这是延迟执行任务,他只能执行一次。
创建这个线程池时有3个参数:

  1. 执行任务
  2. 延迟 n秒执行
  3. 配合2执行的时间单位

4.4.2 固定频率执行

我们可以让线程以以固定频率间隔n秒执行,创建这个线程池有四个参数:

  1. 执行任务
  2. 延迟n秒执行
  3. 执行定时任务的频率
  4. 配合3执行的时间单位
4.4.2.1 scheduleAtFixedRate
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 创建执行定时任务的线程池
 */
public class ThreadPoolDemo5 {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("添加任务的时间:"+ LocalDateTime.now());

        //2S之后开始执行定时任务,定时任务每4s执行一次
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务:"+LocalDateTime.now());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },2,4,TimeUnit.SECONDS);
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

4.4.2.2 scheduleWithFixedDelay
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 创建执行定时任务的线程池
 */
public class ThreadPoolDemo5 {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("添加任务的时间:"+ LocalDateTime.now());


//        //2S之后开始执行定时任务,定时任务每4s执行一次
//        service.scheduleAtFixedRate(new Runnable() {
//            @Override
//            public void run() {
//                System.out.println("执行任务:"+LocalDateTime.now());
//                try {
//                    Thread.sleep(1000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//        },2,4,TimeUnit.SECONDS);

        //2s之后开始执行,每次执行间隔4s
        service.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务:"+LocalDateTime.now());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },2,4,TimeUnit.SECONDS);
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

4.4.2.3 scheduleAtFixedRate VS scheduleWithFixedDelay

看出这两个方法的区别了吗?

scheduleAtFixedRate是以上一次任务开始的时间,加上任务的执行周期,作为下一次定时任务的开始时间。
【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了
scheduleWithFixedDelay是以上一次任务的结束时间,加上任务的执行周期,作为下次定时任务的开始时间。

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

注意:
在scheduleAtFixedRate中,有时任务的执行时间大于延迟任务设定的时间间隔,那么当任务执行完之后才会开始执行下次任务,此时并不是以设定执行周期来执行任务。

ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("添加任务的时间:"+ LocalDateTime.now());
//        scheduleTest(service);//只执行一次的定时任务

        //2S之后开始执行定时任务,定时任务每4s执行一次
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务:"+LocalDateTime.now());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },2,2,TimeUnit.SECONDS);

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

4.5 单线程的线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 单个线程的线程池
 */
public class ThreadPoolDemo7 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            int finalI=i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("任务名:"+finalI+"线程名:"+Thread.currentThread().getName());
                }
            });
        }
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了
那么肯定有同学有疑问了,单线程的线程池与单线程有什么区别呢?他有什么作用呢?

  1. 首先因为他是线程池,所有它能够提供任务队列和任务管理的功能,防止内存溢出。
  2. 其次它可以自定义拒绝策略(本文最后会讲到拒绝策略)

4.6 定时任务的单线程线程池

import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 创建单线程的执行定时任务的线程池
 */
public class ThreadPoolDemo6 {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        System.out.println("执行任务的时间:"+LocalDateTime.now());
        service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务:"+ LocalDateTime.now());
            }
        },2, TimeUnit.SECONDS);
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

4.7 根据当前CPU生成线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 根据当前设备的配置自动生成线程池
 */
public class ThreadPoolDemo8 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newWorkStealingPool();
        for (int i = 0; i < 100; i++) {
            int finalI=i;
            executorService.submit(()->{
                System.out.println("任务:"+finalI+"线程名:"+Thread.currentThread().getName());
            });
        }
        while (!executorService.isTerminated()){};
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

4.8 ThreadPoolExecutor的使用

4.8.1 Executors自动创建线程池可能存在的问题

根据阿里巴巴Java开发手册规定:
【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

4.8.2 ThreadPoolExecutor使用

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了
参数说明:

  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数(包含核心线程数)
  • keepAliveTime:空闲线程的存活时间
  • TimeUnit:时间单位
  • BlockingQueue:任务队列,用于存储线程池的待执行任务的
  • ThreadFactory:线程工厂,用于生成线程
  • handler:拒绝策略

具体实现:

import java.util.concurrent.*;

/**
 * 手动方式创建线程池
 */
public class ThreadPoolDemo10 {
    public static void main(String[] args) {
        ThreadFactory factory=new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread=new Thread(r);
                return thread;
            }
        };
        ThreadPoolExecutor executor=new ThreadPoolExecutor(2,5,10,
                TimeUnit.SECONDS,new LinkedBlockingDeque<>(2),factory,new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 1; i < 6; i++) {
            int finalI=i;
            executor.submit(()->{
                try {
                    Thread.sleep(10*finalI);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务:"+finalI);
            });
        }
    }
}

4.8.3 线程池的执行流程

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了
关键执行步骤:

  1. 当线程池中有任务时,先判断线程池是否在运行状态;
  2. 判断线程池中的线程数是否小于核心线程数,如果小于则添加工作线程并执行;
  3. 若线程池中的线程数不小于核心线程数,则判断阻塞队列是否已满,如果未满,则添加到阻塞队列,等待工作线程获取执行;
  4. 若阻塞队列已满,则判断线程数是否小于最大线程数,若小于,则添加工作线程并执行;
  5. 否则执行拒绝策略。

4.8.4 拒绝策略

线程池的拒绝策略共有5种(4种JDK提供的+1种自定义拒绝策略):

1.AbortPolicy()

提示异常拒绝执行,是默认的拒绝策略:

import java.util.concurrent.*;

/**
 * 手动方式创建线程池
 */
public class ThreadPoolDemo10 {
    public static void main(String[] args) {
        ThreadFactory factory=new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread=new Thread(r);
                return thread;
            }
        };
        ThreadPoolExecutor executor=new ThreadPoolExecutor(2,2,10,
                TimeUnit.SECONDS,new LinkedBlockingDeque<>(2),factory,new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 5; i++) {
            int finalI=i;
            executor.submit(()->{
                try {
                    Thread.sleep(10*finalI);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务:"+finalI);
            });
        }
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了
2.DiscardPolicy()

丢弃任务但不抛出异常。如果线程数大于最大核心线程数,则后续提及的任务都会被丢弃,且是静默丢弃。

import java.util.concurrent.*;

/**
 * 手动方式创建线程池
 */
public class ThreadPoolDemo10 {
    public static void main(String[] args) {
        ThreadFactory factory=new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread=new Thread(r);
                return thread;
            }
        };
        ThreadPoolExecutor executor=new ThreadPoolExecutor(2,2,10,
                TimeUnit.SECONDS,new LinkedBlockingDeque<>(2),factory,new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 5; i++) {
            int finalI=i;
            executor.submit(()->{
                try {
                    Thread.sleep(10*finalI);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务:"+finalI);
            });
        }
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

3.DiscardOldestPolicy()

丢弃阻塞队列最前面的任务,然后重新提交被拒绝的任务。

import java.util.concurrent.*;

/**
 * 手动方式创建线程池
 */
public class ThreadPoolDemo10 {
    public static void main(String[] args) {
        ThreadFactory factory=new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread=new Thread(r);
                return thread;
            }
        };
        ThreadPoolExecutor executor=new ThreadPoolExecutor(2,2,10,
                TimeUnit.SECONDS,new LinkedBlockingDeque<>(2),factory,new ThreadPoolExecutor.DiscardOldestPolicy());
        for (int i = 0; i < 5; i++) {
            int finalI=i;
            executor.submit(()->{
                try {
                    Thread.sleep(10*finalI);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务:"+finalI);
            });
        }
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

4.CallerRunsPolicy()

如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务。

import java.util.concurrent.*;

/**
 * 手动方式创建线程池
 */
public class ThreadPoolDemo10 {
    public static void main(String[] args) {
        ThreadFactory factory=new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread=new Thread(r);
                return thread;
            }
        };
        ThreadPoolExecutor executor=new ThreadPoolExecutor(2,2,10,
                TimeUnit.SECONDS,new LinkedBlockingDeque<>(2),factory,new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 0; i < 5; i++) {
            int finalI=i;
            executor.submit(()->{
                try {
                    Thread.sleep(10*finalI);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务:"+finalI);
            });
        }
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

5.自定义拒绝策略

除了上面四种JDK提供的拒绝策略外,我们还可以自定义拒绝策略,具体实现:

import java.util.concurrent.*;

/**
 * 手动方式创建线程池
 */
public class ThreadPoolDemo10 {
    public static void main(String[] args) {
        ThreadFactory factory=new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread=new Thread(r);
                return thread;
            }
        };
        //自定义拒绝策略
        ThreadPoolExecutor executor=new ThreadPoolExecutor(2, 2, 10,
                TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), factory, new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.out.println("我执行了自定义拒绝策略");
            }
        });
        for (int i = 0; i < 5; i++) {
            int finalI1 = i;
            executor.submit(()->{
                try {
                    Thread.sleep(100*finalI1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务名:"+Thread.currentThread().getName());
            });
        }
    }
}

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了

五、线程池的状态

【多线程】线程池的七种创建方式,线程池的拒绝策略,线程池看这篇就够了可以看到线程池的状态共有5种,分别为:

  • RUNNING:线程池创建之后的状态,这种状态下可以执行任务;
  • SHUTDOWN:该状态下线程池不再接受新任务,但是会将工作队列中的任务执行结束;
  • STOP:该状态下线程池不再接受新任务,并且会中断线程;
  • TIDYING:该状态下所有任务都已终止,将会执行terminated方法;
  • TEIMINATED:执行完terminated方法之后。

5.1 Shutdown VS ShutdownNow

  • shutdown执行时线程池终止接收新任务,并且会将任务队列中的任务处理完;
  • shutdoNow执行时线程池终止接收新任务,并且会终止执行任务队列中的任务。

相关文章

暂无评论

暂无评论...