A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始


1 谈谈线程池

在之前,我们在需要使用线程的时候就去创建一个线程,可谓是非常简便了。但是,当并发的线程数量多了之后,频繁创建线程会令系统的效率大大下降。

那有没有办法可以令我们复用线程?线程在执行完一个任务之后,不会销毁,进而执行其他的任务呢?

事实上,我们可以通过线程池实现相应的功能。在 Java 中,线程池的顶级接口是 Executor,但它并不是线程池的具体实现,真正的线程池实现类为 ThreadPoolExecutor。

我们可以向线程池中传递任务以获得执行,可传递的任务有以下两种,分别是通过 Runnable 实现的任务与通过 Callable 实现的任务,这两者之间的区别为 Runnable 没有返回值但 Callable 有返回值。

2 四种线程池

在 Java 提供了四种线程池的具体实现,分别如下:

newCachedThreadPool:创建一个可缓存线程池
newFixedThreadPool:创建一个定长线程池
newScheduledThreadPool:创建一个定长线程池
newSingleThreadExecutor:创建一个单线程化的线程池
下面对这四种线程池进行简单介绍。

newCachedThreadPool
newCachedThreadPool 是一个可缓存线程池。当调用 execute 方法时,会重用以前构造的线程。该线程池的线程数量并不固定,且线程数量的最大值为 Integer.MAX_VALUE。线程池中的空闲线程有超时限制,这个时间为60秒,超过60秒闲置线程就会被回收。

该线程池适合执行大量耗时较少的任务,当线程池处于闲置状态时,线程池中的所有线程都会因为超时从而被回收。

package test;

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

public class ThreadPoolTest {

        public static void main(String[] args) {
                ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
               
                for(int i = 0;i < 10;i++) {
                        int index = i;
                        try {
                                Thread.sleep(100);
                        } catch (InterruptedException e) {
                                e.printStackTrace();
                        }
                       
                        cachedThreadPool.execute(() -> System.out.println("第" + index + "个任务,当前执行任务的线程为:" + Thread.currentThread().getName()));
                }
        }
}

结果如下:



我们可以发现,事实上执行我们任务的都是同一个线程,这是因为当我们执行下一个任务时,上一个任务已经执行完成,可以复用上一个任务使用的线程,不需要新建线程。

newFixedThreadPool
newFixedThreadPool 是一个定长线程池,该线程池可以指定工作线程数量,每当我们提交一个任务时,就会创建一个工作线程,且当工作线程处于空闲状态时并不会被回收。当工作线程的数量超过最大值时,会将提交的任务存放进没有大小限制的队列中。

newFixedThreadPool 只有核心线程,且这些核心线程并不会被回收,故其能够快速响应外界请求。

package test;

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

public class ThreadPoolTest {

        public static void main(String[] args) {
                //最大线程个数为3
                ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
               
                for(int i = 0;i < 10;i++) {
                        int index = i;
                        fixedThreadPool.execute(() -> {
                                System.out.println("第" + index + "个任务,当前执行任务的线程为:" + Thread.currentThread().getName());
                                try {
                                        Thread.sleep(100);
                                } catch (InterruptedException e) {
                                        e.printStackTrace();
                                }
                        });
                }
        }
}

结果如下:



我们可以发现,由于我们设置的最大线程为3,故当线程池的工作线程等于3个时,即使有新任务进来且无空闲线程,也不会新建线程,而是会将其存放入队列中等待工作线程进行消费。

newScheduledThreadPool
newScheduledThreadPool 是一个定长线程池,它的核心线程数量是固定的,而非核心线程数是没有限制的,且当非核心线程闲置时会被回收。

newScheduledThreadPool 可以延迟运行任务或定时执行任务,适合于执行定时任务或执行周期性任务。

package test;

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

public class ThreadPoolTest {

        public static void main(String[] args) {
                ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
                System.out.println("开始时间" + System.currentTimeMillis());
                scheduledThreadPool.schedule(() -> System.out.println("执行任务的时间" + System.currentTimeMillis()),5,TimeUnit.SECONDS);
        }
}

结果如下:



newScheduledThreadPool 确实起到延时执行任务的作用。

newSingleThreadExecutor
newSingleThreadExecutor 是一个单线程化的线程池。该线程池内部只有一个核心线程,任务会存放进一个无界队列中让该线程顺序执行。newSingleThreadExecutor 的特点就是确保所有任务在同一线程内顺序执行。

package test;

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

public class ThreadPoolTest {

        public static void main(String[] args) {
                ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
                for(int i = 0;i < 10;i++) {
                        int index = i;
                        singleThreadExecutor.execute(() -> System.out.println("第" + index + "个任务被执行"));
                }
        }
}

结果如下:



可见 newSingleThreadExecutor 保证了上述线程的顺序化执行。

3 线程池的原理

看到这里,可能有的读者会非常疑惑。咦,博主,你之前不是说真正的线程池实现类为 ThreadPoolExecutor 吗,我看你介绍了四种线程池,怎么就没提到 ThreadPoolExecutor 呢?别急,且听我慢慢解释。

我们以 newCachedThreadPool 为例子进行讲解,当你执行下面这行代码时

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

其实真正执行的是 ThreadPoolExecutor 的构造方法,换言之,四种线程池底层都是通过 ThreadPoolExecutor 来进行初始化的。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

其它三种线程池的实现如下:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

        public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

    //ScheduledThreadPoolExecutor继承了ThreadPoolExecutor且实现了ScheduledExecutorService接口
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

那我们再来看看在 newCachedThreadPool 中调用的 ThreadPoolExecutor 的构造函数

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
}

它真正调用的是这个构造函数

public ThreadPoolExecutor(int corePoolSize,  
                              int maximumPoolSize,  
                              long keepAliveTime,  
                              TimeUnit unit,  
                              BlockingQueue<Runnable> workQueue,  
                              ThreadFactory threadFactory,  
                              RejectedExecutionHandler handler)

每个参数的具体含义如下:

corePoolSize:核心池大小,当线程池的线程数量超过了这个值,会将新的任务放置在等待队列中
maximumPoolSize:线程池最大线程数量,即线程池能创建的最大线程数
keepAlivertime:当活跃线程数大于核心线程数时,多余线程的最大存活时间
unit:存活时间的单位
workQueue:一个存放任务的阻塞队列
threadFactory:线程工厂,用来创建线程
handler:当线程池的任务缓存队列已满并且线程池中的线程数目达到 maximumPoolSize 且仍有任务到来时执行的任务拒绝策略

4 线程池的优点

线程池可以重用线程,以避免线程的频繁创建和销毁带来的性能开销
可以有效控制线程池的并发数,有效避免大量的线程争夺 CPU 资源而造成堵塞
线程池可以对线程进行管理,例如可以提供定时、定期、单线程、并发数控制等功能
————————————————

原文链接:「Geffin」https://blog.csdn.net/Geffin/article/details/105231333

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马