线程池ThreadPoolExecutor

线程池的好处 
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统 的稳定性,使用线程池可以进行统一的分配,调优和监控。 

Executor框架的两级调度模型 
JAVA线程被一对一映射为本地操作系统线程。JAVA线程启动时会创建一个本 地操作系统线程,当JAVA线程终止时,对应的操作系统线程也被销毁回收,而操作系统会调度所有线程 并将它们分配给可用的CPU。 在上层,JAVA程序会将应用分解为多个任务,然后使用应用级的调度器(Executor)将这些任务映射成 固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。

主要接口:

public interface Executor { void execute(Runnable command); }

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
<T> Future<T> submit(Callable<T> task);

<T> Future<T> submit(Runnable task, T result);

Future<?> submit(Runnable task);
}

另外该接口有两个重要的实现类:

ThreadPoolExecutor与ScheduledThreadPoolExecutor。

其中ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务;而 ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行任务,或者定期执行命令 

线程池其实就是生成者消费者模式。

ThreadPoolExecutor

    //主池控制状态ctl是一个原子整数
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

    private static final int COUNT_BITS = Integer.SIZE - 3;

    private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;


//线程池的工作状态
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

   private final BlockingQueue<Runnable> workQueue;   //阻塞队列

    private final ReentrantLock mainLock = new ReentrantLock(); //锁,保证并发安全

    private final HashSet<Worker> workers = new HashSet<>(); //工作集合


    private final Condition termination = mainLock.newCondition();  //关联队列

    private int largestPoolSize;  //最大线程数

    private long completedTaskCount;  //完成任务的数量

    private volatile long keepAliveTime;  //超出核心线程线程数的线程如果处于空闲最大的存活时间

    private volatile ThreadFactory threadFactory;  //创建线程的工厂

    private volatile RejectedExecutionHandler handler; //拒绝策略

    private volatile boolean allowCoreThreadTimeOut; //如果为false(默认),则核心线程即使在空闲时也保持活动状态。如果为true,则核心线程使用keepAliveTime超时等待工作。

    private volatile int corePoolSize; //核心线程数

    private volatile int maximumPoolSize;//最大池大小。由于工作计数实际上存储在计数位中,因此有效限制为maximumPoolSize&count_MASK。


    private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();   //默认拒绝策略



//将线程封装成worker中,通过worker调用线程的run方法实现线程调度

private final class Worker      
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
       
       final Thread thread;

       Runnable firstTask;

      volatile long completedTasks;
    }


Execute()方法

包含两个概念字段
workerCount,指示线程的有效数量
runState,指示是否正在运行、正在关闭等
public void execute(Runnable command) {
        if (command == null)     //1.如果为空则抛出空指针异常
            throw new NullPointerException();
        int c = ctl.get();      //获取线程的状态
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))//2.如果小于核心线程数,就直接封装成worker,并调用线程的run方法
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {  //3.如果线程数大于核心线程数就尝试加入阻塞队列,如果加入队列成功
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command)) //(因为自上次检查以来,已有的已死亡)或自进入此方法后,池已关闭。再次检查(因为线程入队的过程中可能线程池停止或其它问题),如果线程池不处于运行状态,就把线程移除拒绝掉
                reject(command);
            else if (workerCountOf(recheck) == 0)//如果没有线程,则启动新线程。
                addWorker(null, false);
        }
        else if (!addWorker(command, false)) //4.如果阻塞队列也满了,尝试加入如果未超过最大线程数就加入成功,否则执行饱和的拒绝策略。
            reject(command);
    }

RejetedExecutionHandler:饱和策略 
当队列和线程池都满了,说明线程池处于饱和状态,那么必须对新提交的任务采用一种特殊的策略来进 行处理。这个策略默认配置是AbortPolicy,表示无法处理新的任务而抛出异常。JAVA提供了4中策略:

1、AbortPolicy:直接抛出异常
2、CallerRunsPolicy:只用调用所在的线程运行任务

3、DiscardOldestPolicy:丢弃队列里近的一个任务,并执行当前任务。
4、DiscardPolicy:不处理,丢弃掉。

线程池大小分配    线程池究竟设置多大要看你的线程池执行的什么任务了,CPU密集型、IO密集型、混合型,任 务类型不 同,设置的方式也不一样。    
任务一般分为:CPU密集型、IO密集型、混合型,对于不同类型的任务需要分配不同大小的线 程池。 3.1)CPU密集型    尽量使用较小的线程池,一般Cpu核心数+1 
3.2)IO密集型    方法一:可以使用较大的线程池,一般CPU核心数 * 2    
方法二:(线程等待时间与线程CPU时间之比 + 1)* CPU数目 3.3)混合型    可以将任务分为CPU密集型和IO密集型,然后分别使用不同的线程池去处理,按情况而定 

Executors可以创建3种类型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadExecutor 和CachedThreadPool

SingleThreadExecutor

 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
核心线程数和最大线程数一样,但是阻塞队列是无界的,所以的线程都会阻塞,资源消耗太大

FixedThreadExecutor

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

固定数量的线程,同样是核心线程数大于最大线程数。队列无界

CachedThreadPool

 public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }
无界线程池意味着没有工作队列,任务进来就执行,线程数量不够就创建,与前面两个的区别是:空闲 的线程会被回收掉,空闲的时间是60s。这个适用于执行很多短期异步的小程序或者负载较轻的服务 器。 

SynchronousQueue 也是一个队列来的,但它的特别之处在于它内部没有容器,一个生产线程,当它生产产品(即put的时 候),如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞,等待一个消费线 程调用take操作,take操作将会唤醒该生产线程,同时消费线程会获取生产线程的产品(即数据传 递),这样的一个过程称为一次配对过程(当然也可以先take后put,原理是一样的)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值