JAVA之线程池的基本原理

说起线程池大家肯定不会陌生,在面试中属于必问的问题之一,特别是对于“高并发”有较高要求的企业,基本是必问点。网上关于线程池的文章和视频很多,本篇文章旨在帮助大家快速了解和掌握线程池的基本原理,对于高级应用不过多涉及。

一、并发队列

1. 并发队列概念

并发队列是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。

两种队列区别

入队时

非阻塞队列:当向队列中放入10个元素,此时队列已满,再放入第11个元素数据就会丢失。

阻塞队列:当队列已满了的时候,此时会进行等待,什么时候队列中有出队的元素,那么第11个再放进去。

出队时

非阻塞队列:如果队列中没有元素了,此时进行出队操作,往外取元素,得到的就是null

阻塞队列:当队列中没有元素时,如果此时进行出队操作会等待,什么时候放进去,什么时候再取出来。

特别地,线程池就是基于阻塞队列实现的。

二、线程池简介

线程池是一种多线程处理形式,处理过程中将任务添加到队列,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务。执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

简单来说,线程池就是线程的集合。

三、为什么需要线程池

 为了便于分析,假设各阶段所花时间如上所示(当然线程各阶段实际所花时间极短,为毫秒级)。如果我们能省略其他阶段,每次线程直接运行任务,这样就可以单个线程处理任务就可以节省5秒。要实现这样的设想,我们可以使用线程池来处理,因为线程池中的线程是事先创建好的大量空闲线程,当队列中的任务进入外汇返佣线程池中,线程可以直接执行任务,执行完成后释放资源,继续处理下一任务。

举例来看:现有100个任务需要处理,一次最多创建10个线程。如果采用普通方式,一次创建10个线程处理10个任务,总共需60秒,而采用线程池的方式,一次执行10个任务,总共需要10秒。

综上所述:我们可以很明显的看出线程池在处理任务量极大的高并发系统中,具有很大的优势。

四、线程池的原理

1. ThreadPoolExecutor核心类

 线程池的最上层接口是Executor,这个接口定义了一个核心方法execute(Runnablecommand),这个方法是用来传入任务的,最后被ThreadPoolExecutor类实现。而且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);

特别地说明:

workQueue一般有以下三种阻塞队列:

SynchronousQueue:直接提交,默认使用队列

ArrayBlockingQueue:有界队列

LinkedBlockingQueue:无界队列

threadFactory是当队列已满,但线程总数量<最大线程池大小时,线程池中用来创建新线程的线程工厂。一般有下列三种类型:

ArrayBlockingQueue:有界线程安全的阻塞队列。

LinkedBlockingQueue:并发安全的阻塞队列。

SynchronousQueue:同步队列。

handler触发时,有以下四种拒绝处理策略:

hreadPoolExecutor.AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。

ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

线程池实例

public class test02 {

    public static void main(String[] args) {

        ThreadPoolExecutor pool =

        new ThreadPoolExecutor(1,2,3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3));

        //利用线程池中的线程开始执行任务

        //执行第一个任务

        pool.execute(new TestThread());

        //队列有三个任务等待

        pool.execute(new TestThread());

        pool.execute(new TestThread());

        pool.execute(new TestThread());    

        //执行第五个任务

        pool.execute(new TestThread());

        //执行第六个任务,拒绝任务报错

        //pool.execute(new TestThread());

        //当前线程池中有2个线程:1个核心线程 + 1个新创建的线程 = 最大线程数

        //关闭线程池

        pool.shutdown();

    }

}

class TestThread implements Runnable{

    @Override

    public void run() {

        System.out.println(Thread.currentThread().getName());

    }

}

       首先创建一个最简单类型的线程池,构造方法只有五个参数,每个参数意义如下:

1:核心线程数

2:最大线程数

3:空闲时间。新创建的线程执行任务后等待新任务的空闲时间

TimeUnit.SECONDS:时间单位,秒

new LinkedBlockingDeque:阻塞队列,长度为3

不执行第6条任务时的执行结果如下:

执行第6条任务时执行结果如下:

分析代码执行过程:

       现有一线程池,里面只有一个核心线程thread1,第一个任务进入线程池中,由thread1执行,而2-4号线程处在队列中等待执行,当5号任务提交时,根据原理图,此时满足队列已满,且核+<=最大,所以创建新线程thread2,由thread1thread2分摊执行任务,由运行结果也可以看出,确实是分摊任务。

       当加上第6条的任务时,根据原理图,此时队列已满,且核+>最大,没有多余的线程执行任务,队列也无法装入,就会报错,拒绝任务。

五、线程池的分类

线程池可分为以下四类:

1. 可缓存:newCachedThreadPool

作用:创建一个根据需要创建新线程池的线程池。当旧线程释放资源后就可以使用旧线程。

特点:线程数灵活最大值为INTER.MAX_VALUE,底层采用一个近似无边界队列

2. 定长:newFixedThreadPool

作用:创建一个可重用固定线程数的线程池,以共享的无界队列来运行这些线程。

特点:线程处于一定量,可以很好的控制并发量

3. 定时:newScheduleThreadPool

作用:创建一个可延迟或延期运行的线程池。

特点:线程池中具有指定数量的线程,可定时或延迟执行,适用于周期性执行任务的场景。

4. 单例:newSingleThreadExecutor

作用:创建一个只有一个线程的线程池。且线程的存货时间是无限的,当该线程正繁忙时,对于新任务会进入无界的阻塞队列中。

特点:适用于一个一个任务执行的场景。

线程池四种创建方式

newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

public static void main(String[] args) {

 //可以缓存的线程池

 ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

 for (int i = 0; i < 100; i++) {

 newCachedThreadPool.execute(new Runnable() {

@Override

public void run() {

                        try {

                          Thread.sleep(100);

} catch (Exception e) {

// TODO: handle exception

}

                      System.out.println(Thread.currentThread().getId());

}

});

}

}

线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。 

newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

public static void main(String[] args) {

// 控制并发数的线程池

ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);

for (int i = 0; i < 10; i++) {

newFixedThreadPool.execute(new Runnable() {

@Override

public void run() {

System.out.println(Thread.currentThread().getName());

}

});

}

}

newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。

public static void main(String[] args) {

// 可以定时线程池

ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(3);

for (int i = 0; i < 10; i++) {

newScheduledThreadPool.schedule(new Runnable() {

public void run() {

System.out.println(Thread.currentThread().getName());

}

}, 3, TimeUnit.SECONDS);//延迟3秒执行

}

}

newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();

for (int i = 0; i < 10; i++) {

final int x = i;

newSingleThreadExecutor.execute(new Runnable() {

@Override

public void run() {

System.out.println(Thread.currentThread().getName()+":"+x);

}

});

}

猜你喜欢

转载自www.cnblogs.com/benming/p/11712749.html