13.线程池

1.为什么要使用线程池
(1).每个任务都新开一个线程处理

public class ForLoop {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Task());
            thread.start();
        }
    }

    static class Task implements Runnable {
        @Override
        public void run() {
            System.out.println("执行了任务");
        }
    }
}

上述代码开启10个线程执行任务,但是如果任务数上升为1000个或者10000个,那么再开启1000个或者10000个线程去执行显然不合理,因为线程的创建和销毁是很消耗系统资源的。所以引入线程池可以避免反复创建和销毁线程,增加系统的开销。第二是过多的线程会占用内存,会抛出OOM。

(2).使用线程池的好处

  • 重用存在的线程,减少对象创建、消亡的开销,性能佳
  • 加快响应速度
  • 统一管理线程

2.创建线程池
(1).创建线程池常用的类Executors

  • newFixedThreadPool:创建一个定长的线程池,可以控制线程的最大并发数,超出的线程会在队列中等待。
  • newSingleThreadExecutor:创建一个单线程化的线程池,使用一个唯一的工作线程执行任务,保证所有任务按照指定顺序执行。
  • newCachedThreadPool:创建一个可缓存的线程池,如果线程池的大小超过了需要,可以灵活回收空闲线程,如果没有可回收线程,则新建线程执行任务。
  • newScheduledThreadPool:创建一个定长的线程池,支持定时、周期性的任务执行。

(2).newFixedThreadPool

public class FixedThreadPoolOOM {

    private static ExecutorService executorService = Executors.newFixedThreadPool(1);
    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executorService.execute(new SubThread());
        }
    }
    
    static class SubThread implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(1000000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
	at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
	at com.example.study.app.FixedThreadPoolOOM.main(FixedThreadPoolOOM.java:17)

(3).newSingleThreadExecutor

public class SingleThreadExecutor {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }

    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        }
    }
}

(4).newCachedThreadPool

public class CachedThreadPool {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }

    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        }
    }
}

(5).newScheduledThreadPool

public class ScheduledThreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
        //threadPool.schedule(new Task(), 5, TimeUnit.SECONDS);
        //周期性地执行线程任务
        threadPool.scheduleAtFixedRate(new Task(), 1, 3, TimeUnit.SECONDS);
    }

    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        }
    }
}

(6).钩子方法

public class ThreadPoolHook extends ThreadPoolExecutor {
    public ThreadPoolHook(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        System.out.println("任务执行前需要做的事情");
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        System.out.println("任务执行后需要做的事情");
    }

    public static void main(String[] args) {
        ThreadPoolHook threadPoolHook = new ThreadPoolHook(10,10,60,TimeUnit.SECONDS,new LinkedBlockingDeque<>());
        for(int i = 0;i<10;i++){
            threadPoolHook.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("任务正在执行");
                }
            });
        }
    }
}
任务执行前需要做的事情
任务正在执行
任务执行后需要做的事情
任务执行前需要做的事情
任务正在执行
任务执行后需要做的事情
任务执行前需要做的事情
任务正在执行
任务执行前需要做的事情
任务执行前需要做的事情
任务正在执行
任务执行后需要做的事情
任务正在执行
任务执行后需要做的事情
任务执行后需要做的事情
任务执行前需要做的事情
任务正在执行
任务执行后需要做的事情
任务执行前需要做的事情
任务正在执行
任务执行后需要做的事情
任务执行前需要做的事情
任务正在执行
任务执行后需要做的事情
任务执行前需要做的事情
任务正在执行
任务执行后需要做的事情
任务执行前需要做的事情
任务正在执行
任务执行后需要做的事情

3.线程池的核心参数
(1).线程池最核心的类ThreadPoolExecutor
ThreadPoolExecutor参数最多的构造方法如下,其他的构造方法都是调用这个构造方法来实例化的。

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

(2).corePoolSize
核心线程数量,线程池创建时不会创建线程,有任务提交后才会创建,创建的核心线程无论是否空闲,都不会被销毁。

(3).maxPoolSize
最大线程数,线程池允许创建的最大线程数量。

(4).workQueue
阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。常见的阻塞队列有、有界队列ArrayBlockingQueue、无限队列LinkedBlockingQueue、以及SynchronousQueue。
当提交一个新的任务到线程池时,线程池会根据当前线程池中正在运行的线程数量来决定该任务的处理方式。处理方式总共有三种,直接切换、使用无限队列、使用有界队列。

  • 直接切换常用的队列就是SynchronousQueue。
  • 使用无限队列就是使用基于链表的队列,比如LinkedBlockingQueue。如果使用这种方式,线程池中创建的最大线程数就是corePoolSize,此时maximumPoolSize不会起作用。当线程池中所有的核心线程都是运行状态时,提交新任务,就会放入等待队列中。
  • 使用有界队列就是使用基于数组的队列,比如ArrayBlockingQueue。如果使用这种方式,可以将线程池的最大线程数量限制为maximumPoolSize,降低资源的消耗,但是,这种方式使得线程池对线程的调度更困难,因为线程池和队列的容量都是有限的。

corePoolSize、maxPoolSize和workQueue三个参数的关系如下所示。

条件动作
n < corePoolSize创建核心线程执行任务,即使线程池中的其他线程是空闲的
corePoolSize =< n <= maxPoolSize 且队列未满将任务放入队列
corePoolSize =< n <= maxPoolSize 且队列满创建新线程执行任务
maxPoolSize =< n 且队列未满将任务放入队列
maxPoolSize < n 且队列满通过拒绝策略参数rejectHandler来指定处理策略

(5).keepAliveTime
线程数比核心线程数多且多余的线程没有执行任务处于空闲状态时,可以设置一个存活时间,如果有任务进来就执行,如果没有任务进来就会被销毁,这个时间就是keepAliveTime。

(6).unit
keepAliveTime的时间单位。

(7).threadFactory
创建线程的工厂,默认使用Executors.defaultThreadFatory()。当使用默认的工厂来创建线程时,会使新创建的线程具有相同的优先级,并且是非守护的线程,同时也设置了线程的名称。

(8).rejectHandler
拒绝处理任务时的策略,如果workQueue阻塞队列满了,并且没有空闲的线程池,此时,继续提交任务,需要采取一种策略来处理这个任务。线程池总共提供了如下四种策略。

  • AbortPolicy:直接抛出异常,这也是默认的策略。
  • DiscardPolicy:直接丢弃当前任务。
  • DiscardoldestPolicy:丢弃队列中最靠前的任务并执行当前任务。
  • CallerRunsPolicy:用调用者所在的线程来执行任务。

4.线程池状态

  • RUNNING:运行状态,能接收新提交的任务,并且也能处理阻塞队列中的任务。
  • SHUTDOWN:关闭状态,不能再接收新提交的任务,但是可以处理阻塞队列中已经保存的任务,当线程池处于Running状态时,调用shutdown()方法会使线程池进入该状态。
  • STOP:停止状态,不能接收新任务,也不能处理阻塞队列中已经保存的任务,会中断正在处理任务的线程,如果线程池处于RUNNING或SHUTDOWN状态,调用shutdownNow()方法,会使线程池进入该状态。
  • TIDYING:如果所有的任务都已经终止,线程池中的工作线程数量为0且阻塞队列为空,线程池就会进入该状态。
  • TERMINATED:处于TIDYING状态的线程池调用terminated()方法,会使线程池进入该状态。

5.ThreadPoolExecutor提供的启动和停止任务的方法

  • execute():提交任务,交给线程池执行。
  • submit():提交任务,能够返回执行结果。
  • shutdown():关闭线程池,等待任务都执行完。
  • shutdownNow():立即关闭线程池,不等待任务执行完。

6.ThreadPoolExecutor提供的适用于监控的方法

  • getTaskCount():线程池已执行和未执行的任务总数。
  • getCompletedTaskCount():已完成的任务数量。
  • getPoolSize():线程池当前的线程数量。
  • getCorePoolSize():线程池核心线程数。
  • getActiveCount():当前线程池中正在执行任务的线程数量。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值