关于ThreadPool: 线程池

intro:

如果不使用线程池,那么每个任务都是新开一个线程进行处理

--- drawback:

  • 开销大,希望固定数量的线程依次执行,避免反复创建&销毁线程所带来的问题

  • 过多的线程会占用太多内存

--- benefit:

  • 用少量线程,避免内存占用过多

  • 让线程都保持工作,并且反复执行任务,避免生命周期的损耗

线程池构造方法:

参数:

  • corePoolSize -- int -- 核心线程数;线程池初始化时,是没有任何线程的,在接收到任务后立即创建线程执行任务。线程通常会一直存活,除非exception

  • maxPoolSize -- int -- 当corePoolSize不足以完成任务时 i.e. corePoolSize=5,通常线程池中是有任务队列workQueue的,更多的任务会在任务队列中排序(尽可能不突破corePoolSize的数量),但是如果队列满了却还要新增任务,就要创建线程,最多创建到maxPoolSize的数量

  • keepAliveTime -- long -- 保持存活时间。线程数 > corePoolSize,并且多余的线程空闲的时间超过keepAliveTime,他们就会被终止。(超过 多久 回收多余线程)

  • workQueue -- BlockingQueue -- 任务存储队列 、工作队列

    • 直接交换:SynchronousQueue -- 容量0,任务直接交换 任务量不是特别多,只是将任务通过队列进行中转

    • 无界队列:LinkedBlockingQueue -- 队列很大,不会被放满

      • adv:适用于控制流量突增的情况

      • disadv:处理的速度跟不上任务提交的速度,会造成内存的浪费和OOM异常(内存溢出)

    • 有界的队列: ArrayBlockingQueue -- 满了且不足maxPoolSize去创建线程

  • threadFactory -- 用来创建线程

    • 或者默认创建线程方式 【Executors.defaultThreadFactory()】,默认创建出的线程都是在同一个线程组并且有相同的优先级5,非守护线程

    • 如果自己指定ThreadFactory,那么可以改变线程名,线程组,优先级,是否是守护线程等。

  • Handler -- RejectedExecutionHandler -- 当线程数达到maxPoolSize,使用Handler进行拒绝

 

添加线程规则:

  • 线程数 < corePoolSize,创建一个新线程来运行新任务。

  • 线程数 >= corePoolSize && < maxPoolSize, 将任务放入队列中

  • 队列满,线程数 < maxPoolSize, 创建一个新线程

  • 队列满,线程数 >= maxPoolSize,则拒绝

 

线程池的创建

手动创建线程池更好,可以更加明确线程池的运行规则,避免资源耗尽的风险

FixedThreadPool:

在创建的过程中就固定了线程的数量,在执行的过程中,无论任务量的多少,是不会超过线程个数的

source code:

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

corePoolSize 和maxPoolSize数量相同 导致固定线程数,使用的队列是LinkedBlockingQueue,队列很大不会被放满的

public class FixedThreadPoolTest {
​
    public static void main(String[] args) {
        // 固定线程数的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for(int i = 0; i < 1000; i++){
            executorService.execute(new Task());
        }
    }
}
​
class Task implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}

output:

pool-1-thread-2
pool-1-thread-4
pool-1-thread-3
pool-1-thread-4
pool-1-thread-1
pool-1-thread-2

导致问题: OOM内存溢出异常 ---- OutOfMemory:

例子:

// 演示FixedThreadPool出错的情况出现OOM内存溢出
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());
        }
    }
}
​
class SubThread implements Runnable{
    @Override
    public void run() { // 任务时间尽量延长
        try {
            Thread.sleep(1000000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

SingleThreadExecutor

因为是单一线程运行执行任务,不需要传入任何参数

ExecutorService executorService = Executors.newSingleThreadExecutor();

source code:

corePoolSize 和 maxPoolSize 的数量一致,都是1,并且LinkedBlockingQueue 无限

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

缺点:请求堆积的时候,占用大量内存

 

CachedThreadPool

可缓存线程池,自动回收多余线程。 ---- 任务来了,就创建线程,任务结束,就回收线程

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

source code:

maxPoolSize设置为最大,无限创建线程,当任务特别多创建了很多线程的时候,就会导致OOM

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

 

ScheduledThreadPool

支持定时周期性的执行任务,可以根据时间做一些线程相关工作

public class ScheduledThreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService =
                Executors.newScheduledThreadPool(10);
        //周期性的 线程池支持schedule()  -- 延迟5s执行任务
        scheduledExecutorService.schedule(new Task(), 5, TimeUnit.SECONDS);
        //初始延迟1s,之后每隔3s
        scheduledExecutorService.scheduleAtFixedRate(new Task(), 1,3, TimeUnit.SECONDS);
    }
}

source code:

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
          new DelayedWorkQueue());
}

 

summary for normal auto-Thread pool

ParameterFixedThreadPoolCachedThreadPoolScheduledThreadPoolSingleThread
corePoolSizeconstructor-arg0constructor-arg1
maxPoolSizesame as coreInteger.MAX_VALUEInteger.MAX_VALUE1
keepAliveTime0s60s0s0s

 

workingStealingPool:

1.8之后加入的,子任务,窃取

 

手动创建线程池:

  • 线程数量设定:

    • CPU密集型,最佳线程数为CPU核心数1-2倍 i.e. 加密、计算hash等, -- 8 核,可以设置为8-16,因为CPU都是满负荷计算,分配给CPU就可

    • 耗时IO型,最佳线程数一般会大于CPU核心数很多倍 i.e. 读写数据库,文件,网络,涉及到读取很多CPU都是处于等待状态不工作的

  • Brain Goetz 计算方法:

    线程数 = CPU核心数*(1+平均等待时间/平均工作时间)

 

停止线程池:

shutdown()

虽然是关闭线程池,但不是立即的关闭,是将存量Task继续执行完,同时线程池就不再接受其他任务,抛出拒绝异常。RejectedExecutionException

public class ShutDown {
​
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new ShutDownTask());
        }
        try {
            Thread.sleep(1500);
            executorService.shutdown();  // 关闭线程池,但非立即关闭
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //shutdown不是立即的停止,但是如果shutdown后,再提交新的任务是会抛出异常的
        executorService.execute(new ShutDownTask());
    }
}
​
class ShutDownTask implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

output --- shutdown后提交任务,抛出异常

pool-1-thread-7
pool-1-thread-3
Exception in thread "main" java.util.concurrent.RejectedExecutionException
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:1768)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:767)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:658)
    at pracThreadPool.ShutDown.main(ShutDown.java from InputFileObject:20)
pool-1-thread-4
pool-1-thread-8

 

isShutdown()

return boolean 判断是否进入停止状态,非判断是否已经停止

ExecutorServiceObject.isShutDown();

 

isTerminated()

return boolean 判断是否已经停止处理任务(完全终止)。 【vs isShutdown():isTerminated不是判断状态而是真正的停止】

 

awaitTermination(time,TimeUnit)

只有三种情况会有返回值return,返回前是Blocked状态。

  • 所有任务都执行完毕

  • 等待时间到了

  • 等待时候被中断,会抛出interrupted exception

return boolean 用来测试在参数时间内,线程是否会完全终止的方法,作用:检测非关闭

 

shutdownNow()

立刻关闭。正在执行的线程被interrupted,队列中等待的任务会直接return,return List<Runnable>

public class ShutDown {
​
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(new ShutDownTask());
        }
        try {
            Thread.sleep(1500);
            List<Runnable> runnableList = executorService.shutdownNow();
            // 合理利用runnableList,未执行的线程任务都存储在这个集合中。
            System.out.println(runnableList.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
​
class ShutDownTask implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "被中断了");
        }
    }
}

 

线程池的拒绝

拒绝时机:

  • 当Executor关闭,提交新任务会被拒绝抛出exception --- 已经关闭

  • 当Executor对最大线程和工作队列容量使用有限边界并且已经饱和时 --- 能力不足

拒绝策略:

  • AboerPolicy:提交新任务,抛出异常exception

  • DiscardPolicy:提交新任务,直接忽略, 默默丢弃

  • DiscardOldestPolicy:丢弃最老的、存在时间最久的

  • CallerRunsPolicy:提交任务的线程执行,i.e. 主线程提交给线程池任务,但是线程池不接受,则交给主线程执行。针对提交线程,完成任务也是需要时间的,在完成之后才能再次向线程池提交任务,是更合理的。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值