什么是线程池 线程池实战 线程池Demo练习

目录

1、首先创建一个线程了解线程机制

 2、为什么要用线程池

3、Java Executors类下自带的4种线程池

4、ThreadPoolExecutor的四种构造方法及参数名解析

5、线程池的简单应用

线程池的小Demo

6、线程池的其他注意事项


1、首先创建一个线程了解线程机制

public class TestDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            int i = 1;
            System.out.println("线程开始工作...");
            while (true) {
                try {
                    Thread.sleep(100);
                    System.out.println("线程执行第" + i + "次");
                    i++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(i == 13){
                    System.out.println("线程结束工作");
                    break;
                }
            }
        }).start();
    }
}


 2、为什么要用线程池

但为了合理控制创建线程的数量和优化资源开销,可以通过线程池提供线程资源,不在业务代码中显示创建线程。使用线程池创建线程的优点如下:

1、使线程的创建更加规范,可以合理控制开辟线程的数量;
2、线程的细节管理交给线程池处理;
3、在执行大量异步任务时,由于减少了每个任务的调用开销,再加上线程池提供了一种限制和管理资源和线程的方法,能够明显提升性能;
4、方便统计信息,每个ThreadPoolExecutor可以保存一些基本的统计信息,例如完成的任务数量。


3、Java Executors类下自带的4种线程池

newSingleThreadExexcutor单线程数的线程池(核心线程数=最大线程数=1)
newFixedThreadPool固定线程数的线程池(核心线程数=最大线程数=自定义)
newCacheThreadPool可缓存的线程池(核心线程数=0,最大线程数=Integer.MAX_VALUE)
newScheduledThreadPool支持定时或周期任务的线程池(核心线程数=自定义,最大线程数=Integer.MAX_VALUE)

但需要注意的是,线程池最好不要使用Executors去创建,而要使用ThreadPoolExecutor。主要原因如下:

1、JDK中的Executor框架虽然提供了newFixedThreadPool()、newSingleThreadExecutor()和newCachedThreadPool()等预定义线程池,但都有局限性,不够灵活;
2、由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于明确线程池的运行规则,创建符合业务场景需要的线程池,避免资源耗尽的风险。


4、ThreadPoolExecutor的四种构造方法及参数名解析

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory)
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler)
public ThreadPoolExecutor(int corePoolSize,         //核心线程池大小。
                              int maximumPoolSize,  //最大线程池大小。
                              long keepAliveTime,    //线程的最大空闲时间。
                              TimeUnit unit,         //等待时间单位。
                              BlockingQueue<Runnable> workQueue,  //线程阻塞队列。
                              ThreadFactory threadFactory,         //线程创建工厂。
                              RejectedExecutionHandler handler   //线程拒绝策略。)    

5、线程池的简单应用

在实际的使用中,ThreadPoolExecutor线程池的创建、线程的执行顺序以及数量限制有以下特点:

1、进入核心线程池的线程将立即开始执行。

2、线程数超过核心线程池大小后,后续线程会进入阻塞队列,阻塞队列满了后再进入线程池开始执行,直到数量到达最大线程池大小。

3、阻塞队列满了后,线程池还能接收的线程数 = 最大线程池大小 - 核心线程池大小。

4、线程池能容纳的最大线程数 = 最大线程池大小 + 阻塞队列容量,线程数量超过该值后,后续加入的线程将触发拒绝策略。

5、allowCoreThreadTimeOut的默认值为false,调用allowCoreThreadTimeOut(true)方法后,超过线程最大空闲时间的线程将被销毁。

线程池的小Demo

任务类示例代码↓

import java.text.SimpleDateFormat;
import java.util.Date;

public class WorkerThread implements Runnable{
    private int id;
    // 构造方法
    public WorkerThread(int id) {
        this.id = id;
    }
    @Override
    public void run() {
        //设置日期格式
        SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss.sss");
        System.out.println(format.format(new Date()) + " | The thread " + id + " starts working.");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(format.format(new Date()) + " | The thread " + id + " is down.");
    }
}

测试代码↓

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;

public class SyncTest {
    // 创建线程池
    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(2,3,100, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(3),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {
        SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss.sss");
        for (int i = 0; i < 9; i++) {
            try {
                // 向线程池中添加任务
            executor.execute(new WorkerThread(i));
            System.out.println(format.format(new Date()) +
                    " | The thread " + i + " joined the thread pool.");
                Thread.sleep(100);
            } catch (RejectedExecutionException re){
                System.out.println(format.format(new Date()) + " | The thread " + i + " was rejected.");
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        executor.shutdown();
    }

}

输出结果↓

 运行结果分析↓

1、在上述代码中线程池最大可容纳3(最大线程池大小) + 3(阻塞队列容量) = 6个,共有9个线程希望加入。

2、核心线程池大小2,线程0和1进入核心线程池立刻开始执行。

3、阻塞队列大小为3,线程2、3、4共三个线程将依次进入阻塞队列开始等待。

4、最大线程池大小为3,阻塞队列满后,线程池还能接收的线程数 = 3(最大线程池大小) - 2(核心线程池大小) = 1,所以线程5将进入线程池并开始执行。

5、线程池满了后,后续加入的3个线程将触发拒绝策略,线程6、7、8将被直接拒绝无法执行

6、出现结束运行的线程之后,将开始处理阻塞队列中的等待线程,由于使用的是LinkedBlockingQueue,依照先进先出的原则,先执行最早开始等待的线程,所以按照线程2、3、4的顺序开始执行。


6、线程池的其他注意事项

1、如果核心线程数量大于0,线程池在创建后,会一直存在,直到调用shutdown()方法,才会关闭线程池,所以忘记关闭线程池可能会导致内存溢出;
2、如果程序中不再持有线程池的引用,并且线程池中没有线程时,线程池将会自动关闭,如果希望确保即使忘记调用 shutdown()方法,也可以回收未引用的线程池,那么必须通过设置适当的keep-alive times并设置allowCoreThreadTimeOut(true),或设置核心线程数量为0;
3、ExecutorService是ThreadPoolExecutor的顶层接口,使用线程池中的线程执行每个提交的任务,我们可以使用Executors的工厂方法来创建ExecutorService。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值