请使用线程池创建线程,而勿手动创建线程

在中大型项目中,不可避免地需要执行异步任务,而异步任务的执行则是通过线程而执行的,因此掌握线程是如何创建的则是执行异步任务的第一步。

我们知道,常见的创建线程的方式有:

  1. 继承Thread类,重写run()方法
  2. 实现Runnable接口,重写run()方法
  3. 使用线程池创建线程

一、继承Thread类

public class ThreadOne extends Thread {

    @Override
    public void run() {
        System.out.println("当前线程:" + Thread.currentThread().getName() + "执行了run方法");
    }

    public static void main(String[] args) {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        new ThreadOne().start();
    }
}

输出:

当前线程:main
当前线程:Thread-0执行了run方法

二、实现Runnable接口

public class ThreadTwo implements Runnable{

    @Override
    public void run() {
        System.out.println("当前线程:" + Thread.currentThread().getName() + "执行了run方法");
    }
}
public class Test {

    public static void main(String[] args) {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        new Thread(new ThreadTwo()).start();
    }
}

输出:

当前线程:main
当前线程:Thread-0执行了run方法

三、使用线程池

为什么使用线程池创建线程? 因为线程的创建和销毁是非常消耗资源的,而线程池可以复用之前创建好的线程,因此减少了资源的消耗。

3.1 ThreadPoolExecutor

1. ThreadPoolExecutor类的构造方法

ThreadPoolExecutor构造方法
在这里插入图片描述

参数说明:

  • corePoolSize: 常驻核心线程数,如果大于0,即使本地任务执行完也不会被销毁
  • maximumPoolSize: 线程池能够容纳可同时执行的最大线程数
  • keepAliveTime: 线程池中线程空闲的时间,当空闲时间达到该值时,线程会被销毁, 只剩下 corePoolSize 个线程数量。
  • unit: 空闲时间的单位。一般以TimeUnit类定义时分秒。
  • workQueue: 当请求的线程数大于 corePoolSize 时,线程进入该阻塞队列。
  • threadFactory: 线程工厂,用来生产一组相同任务的线程,同时也可以通过它增加前缀名,虚拟机栈分析时更清晰
  • handler: 执行拒绝策略,当 workQueue 已满,且超过maximumPoolSize 最大值,就要通过这个来处理,比如拒绝,丢弃等,这是一种限流的保护措施。

ThreadPoolExecutor执行流程:
在这里插入图片描述

创建ThreadPoolFactory工厂类:

public final class ThreadPoolFactory {

    private volatile static ThreadPoolExecutor executor;

    private ThreadPoolFactory(){
    }

    /**
     * 创建ExecutorService,单例模式(DCL)
     * @return
     */
    public static ExecutorService getExecutorService(){
        if (executor == null){
            synchronized (ThreadPoolFactory.class){
                if (executor == null){
                    executor = new ThreadPoolExecutor(1, 5, 30, TimeUnit.SECONDS,
                            new LinkedBlockingQueue<>(5),
                            new CustomThreadFactory("自定义线程"),
                            new ThreadPoolExecutor.AbortPolicy());
                }
            }
        }
        return executor;
    }

    /**
     * 自定义线程工厂
     */
    private static class CustomThreadFactory implements ThreadFactory{
        /**
         * 参数 代表是第1个ThreadPoolExecutor产生的
         */
        private final AtomicInteger poolNumber = new AtomicInteger(1);

        private final ThreadGroup threadGroup;

        private final AtomicInteger threadNumber = new AtomicInteger(1);

        public final String namePrefix;

        CustomThreadFactory(String name) {
            SecurityManager s = System.getSecurityManager();
            threadGroup = (s != null) ? s.getThreadGroup() :
                    Thread.currentThread().getThreadGroup();
            if (null == name || "".equals(name.trim())) {
                name = "pool";
            }
            namePrefix = name + "-" + poolNumber.getAndIncrement() + "-thread-";
        }

        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(threadGroup, r,
                    namePrefix + threadNumber.getAndIncrement(),
                    0);
            if (t.isDaemon()) {
                t.setDaemon(false);
            }
            if (t.getPriority() != Thread.NORM_PRIORITY) {
                t.setPriority(Thread.NORM_PRIORITY);
            }
            return t;
        }
    }
}

测试:

public class ThreadPoolFactoryTest {
    public static void main(String[] args) {
        ExecutorService service = ThreadPoolFactory.getExecutorService();

        for (int i = 0; i < 50; i++) {
            service.execute(() -> {
                System.out.println("当前线程: " + Thread.currentThread().getName());
            });
        }
    }
}

输出:由于线程数量过大,触发拒绝策略,抛出异常。
在这里插入图片描述

2. 阻塞队列BlockingQueue:

BlockingQueue的Subtypes:
在这里插入图片描述

从构成关系图可以看出,阻塞队列的实现类主要有4种类型:

  • LinkedBlockingQueue
    无界队列,当不指定队列大小时,将会默认为Integer.MAX_VALUE大小的队列,因此大量的任务将会堆积在队列中,最终可能触发OOM。
  • ArrayBlockingQueue 有界队列,基于数组的先进先出队列,此队列创建时必须指定大小。
  • PriorityBlockingQueue 有界队列,基于优先级任务的,它是通过Comparator决定的。
  • SynchronousQueue 这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务

3、处理策略Handler:

  • AbortPolicy 默认的拒绝策略,抛RejectedExecutionException异常
  • DiscardPolicy 相当大胆的策略,直接丢弃任务,没有任何异常抛出
  • DiscardOldestPolicy 丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列
  • CallerRunsPolicy 提交任务的线程自己去执行该任务

4、线程池的关闭

  • shutdown() : 不会立刻终止线程,等所有缓存队列中的任务都执行完毕后才会终止。
  • shutdownNow() : 立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

3.2 Executors类创建线程

Executors类创建线程池的方法归根结底都是调用ThreadPoolExecutor类,只不过对每个方法赋值不同的参数去构造ThreadPoolExecutor对象。

  1. newCachedThreadPool:创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPool: 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
  3. newScheduledThreadPool: 创建一个定长线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor:
    创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

注意线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式创建。

原因因为上述四个方法的创建的队列大小默认都是Integer.MAX_VALUE,堆积过多的任务请求会可能导致OOM。

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值