Java源码解读系列11—线程池(上,JDK1.8 )

1 概述

  • 一开始听到这么牛逼的名字,博主还以为是操作系统的底层直接提供的,可以用来直接创建线程的。看完源码后才知道,线程池实际上是java.util.concurrent包下面用来管理线程的工具,开发语言是java代码,启动线程还是依赖于底层提供的Thread类的Native方法start0()。

  • 线程池的优点主要有 1)复用线程对象,降低频繁的创建和销毁线程是带来的开销。
    2)方便管理线程。线程池已经封装了线程监管,异常处理,线程创建和销毁等方法,开箱即用。 3)提供并发处理性能。因为线程池中核心线程创建后,在不关闭的前提下,当下次有新的线程访问时,可以直接使用,不需要再重新创建。

  • 线程池的处理流程如下:
    在这里插入图片描述

2 自义线程池构造函数

自定义线程池是其他线程池框架的基础,根据不同workQueue会有不同的使用效果,workQueue都会对其他参数造成影响。因此在了解每个参数意义的前提下,我们需要根据场景去学习。

 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 : 池中允许的最大线程数
keepAliveTime :当任务全部处理完事,大于corePoolSize但是小于等于maximumPoolSize的那部分线程,会在keepAliveTime * unit 到达到后被清除
**unit ** :时间单位
workQueue : 阻塞队列,用于存储大于corePoolSsize的线程
threadFactory:线程工厂
handler:拒绝策略,当workQueue和maximumPoolSize都满了,对新进来的线程采用哪种处理策略

3 线程工程

线程工厂ThreadFactory并没有开启线程的作用,而是给线程池中开启的线程添加属性,比如设置线程命名,查看创建线程数,设置线程为守护线程,设置线程优先级等。ThreadFactory是一个接口

public interface ThreadFactory {
    Thread newThread(Runnable r);
}

在自定义线程池的构造函数中采用以下实现方式

public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
  }
  
 private static final AtomicInteger poolNumber = new AtomicInteger(1);
  
DefaultThreadFactory() {
    //线程组
    SecurityManager s = System.getSecurityManager();
    group = (s != null) ? s.getThreadGroup() :
                          Thread.currentThread().getThreadGroup();
     //定义线程的名字
     //可以根据自己项目的要求定义线程的名字
    namePrefix = "pool-" +
                  poolNumber.getAndIncrement() +
                 "-thread-";
}

4 拒绝策略

当线程池中的资源被用完时,线程池对新添加的的任务线程采用不同的处理策略。RejectedExecutionHandler是一个接口,在线程池中,其实现有4种。

final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
  }

//拒绝策略的接口,JDK自带或者自己根据业务实现的拒绝策略都必须实现这个接口
public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

4.1 AbortPolicy

线程池默认的拒绝策略是AbortPolicy,直接抛出RejectExecutionException异常

public static class AbortPolicy implements RejectedExecutionHandler {

     //无参构造函数
    public AbortPolicy() { }

   //直接抛出异常
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

4.2 DiscardPolicy

将超出的任务丢弃,跟AbortPolicy很相似,只是不抛出异常

 public static class DiscardPolicy implements RejectedExecutionHandler {
      
       //无参构造函数
        public DiscardPolicy() { }

       //不排除异常,也没做任何处理,单纯丢掉
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

4.3 DiscardOldestPolicy

丢弃队列里最近一个任务,并执行当前任务


 public static class DiscardOldestPolicy implements RejectedExecutionHandler {
     
        //无参构造函数
        public DiscardOldestPolicy() { }        
        
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
           //判断线程池是否关闭
            if (!e.isShutdown()) {
               //移除队列种的首个元素
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

4.4 CallerRunsPolicy

使用线程池的调用者来执行超出的任务。比如本机测试中使用main方法调用线程池,那么就会使用main线程来执行超出的任务。

 public static class CallerRunsPolicy implements RejectedExecutionHandler {
         //无参构造函数
        public CallerRunsPolicy() { }

       //使用线程池的调用者来执行超出的任务
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            //判断线程池是否关闭
            if (!e.isShutdown()) {
               //run方法是使用当前线程执行
                r.run();
            }
        }
    }

5 线程池提交任务的方法

5.1 execute()

用于提交不需要返回值的任务

public void execute(Runnable command) {
        ..
{

5.2 submit()

submit方法底层执行实际上是调用execute方法,其主要用于提交需要返回值的任务,返回值的类型为Future,再通过阻塞的get()方法进行获取;也可以使用谷歌Guava包的ListenableFuture类进行异步获取。

 public <T> Future<T> submit(Callable<T> task) {
        //非空校验
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        //调用execute方法
        execute(ftask);
        //返回RunnableFuture对象
        return ftask;
    }

6 线程池的监控

6.1 getCorePoolSize()

获取线程池核心线程数。就是线程池初始化时以参数形式传进去的corePoolSize值。

private volatile int corePoolSize;

 public int getCorePoolSize() {
    return corePoolSize;
}

6.2 getPoolSize()

获取线程池中当前线程的数量

private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}

private final HashSet<Worker> workers = new HashSet<Worker>();


public int getPoolSize() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {

        //当线程池的状态为TIDYING或者TERMINATED时返回true
        return runStateAtLeast(ctl.get(), TIDYING) ? 0
            : workers.size();
    } finally {
        mainLock.unlock();
    }
}

6.3 getQueue().size()

等待队列中的线程数

//等待队列,存储未处理的线程
private final BlockingQueue<Runnable> workQueue;

 public BlockingQueue<Runnable> getQueue() {     
        return workQueue;
 }

7 不同阻塞队列的影响

7.1 LinkedBlockingQueue

LinkedBlockingQueue的无参构造方式是无界的阻塞队列,默认大小是是Integer.MAX_VALUE。可以通过有参构造方法指定队列大小,从而成为有界的。
如果使用无界的阻塞队列,keepAliveTime和maximumPoolSize就变得没有意义。
因为超过corePoolSize的未执行任务会被放入无界队列中。

public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
 }
 
 public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

7.2 ArrayBlockingQueue

ArrayListBlockingQueue是一个有界的等待队列,底层是基于数组实现,因此初始化的时候需要指定数组大小。
超过corePoolSize的未执行任务会被存放在ArrayListBlockingQueue中。
1)当ArrayListBlockingQueue中的数组已满,且多余的线程数+corePoolSize小于等于maximumPoolSize时,线程池会启动新的线程去处理。

public class ThreadPoolApp {

    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1,
                2,
                0,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue(1),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println("线程名字:" +Thread.currentThread().getName());
                }catch (InterruptedException e){
                    e.printStackTrace();
                }

            }
        });

        for (int i = 0; i < 3; i++){
            executor.execute(thread);
        }


        while(true){
            System.out.println("PoolSize: " + executor.getPoolSize());
            System.out.println("CorePoolSize: " + executor.getCorePoolSize());
            System.out.println("QueueSize: " + executor.getQueue().size());
            System.out.println("ActiveCount: " + executor.getActiveCount());
            System.out.println("====================");
            try {
                Thread.sleep(3000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }

    }
}

控制台的打印结果:

PoolSize: 2
CorePoolSize: 1
QueueSize: 1
ActiveCount: 2
====================
线程名字:pool-1-thread-1
线程名字:pool-1-thread-2
线程名字:pool-1-thread-1
PoolSize: 1
CorePoolSize: 1
QueueSize: 0
ActiveCount: 0

2)当ArrayListBlockingQueue中的数组已满,且多余的线程数+corePoolSize大于maximumPoolSize时,线程池会抛出异常。

public class ThreadPoolApp {

    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1,
                2,
                0,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue(1),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println("线程名字:" +Thread.currentThread().getName());
                }catch (InterruptedException e){
                    e.printStackTrace();
                }

            }
        });

        for (int i = 0; i < 4; i++){
            executor.execute(thread);
        }


        while(true){
            System.out.println("PoolSize: " + executor.getPoolSize());
            System.out.println("CorePoolSize: " + executor.getCorePoolSize());
            System.out.println("QueueSize: " + executor.getQueue().size());
            System.out.println("ActiveCount: " + executor.getActiveCount());
            System.out.println("====================");
            try {
                Thread.sleep(3000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }

    }
}

控制台的打印结果:

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Thread[Thread-0,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@6d6f6e28[Running, pool size = 2, active threads = 2, queued tasks = 1, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
	at ThreadPool.ThreadPoolApp.main(ThreadPoolApp.java:89)
线程名字:pool-1-thread-2
线程名字:pool-1-thread-1
线程名字:pool-1-thread-2

7.3 SynchronousQueue

SynchronousQueue是一种无缓冲的等待队列,即不存储任何数据对象。因为每个插入操作put()方法必须等到另一个线程调用take()方法移除,否则插入操作一直处于阻塞状态,因此,并发性能最佳。

  1. 多余的线程数+corePoolSize小于等于maximumPoolSize时,线程池会启动新的线程去处理。
public class ThreadPoolApp {

    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1,
                2,
                0,
                TimeUnit.SECONDS,
                new SynchronousQueue<>(),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println("线程名字:" +Thread.currentThread().getName());
                }catch (InterruptedException e){
                    e.printStackTrace();
                }

            }
        });

        for (int i = 0; i < 2; i++){
            executor.execute(thread);
        }


        while(true){
            System.out.println("PoolSize: " + executor.getPoolSize());
            System.out.println("CorePoolSize: " + executor.getCorePoolSize());
            System.out.println("QueueSize: " + executor.getQueue().size());
            System.out.println("ActiveCount: " + executor.getActiveCount());
            System.out.println("====================");
            try {
                Thread.sleep(3000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }

    }
}

控制台的打印结果:

PoolSize: 2
CorePoolSize: 1
QueueSize: 0
ActiveCount: 2
====================
线程名字:pool-1-thread-2
线程名字:pool-1-thread-1
PoolSize: 1
CorePoolSize: 1
QueueSize: 0
ActiveCount: 0
  1. 多余的线程数+corePoolSize大于maximumPoolSize时,线程池会抛出异常。
public class ThreadPoolApp {

    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1,
                2,
                0,
                TimeUnit.SECONDS,
                new SynchronousQueue<>(),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println("线程名字:" +Thread.currentThread().getName());
                }catch (InterruptedException e){
                    e.printStackTrace();
                }

            }
        });

        for (int i = 0; i < 3; i++){
            executor.execute(thread);
        }


        while(true){
            System.out.println("PoolSize: " + executor.getPoolSize());
            System.out.println("CorePoolSize: " + executor.getCorePoolSize());
            System.out.println("QueueSize: " + executor.getQueue().size());
            System.out.println("ActiveCount: " + executor.getActiveCount());
            System.out.println("====================");
            try {
                Thread.sleep(3000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }

    }
}

控制台的打印结果:

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Thread[Thread-0,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@135fbaa4[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
	at ThreadPool.ThreadPoolApp.main(ThreadPoolApp.java:89)
线程名字:pool-1-thread-1
线程名字:pool-1-thread-2

8 不同线程池条件的影响

8.1 小于等于corePoolSize

启动新的线程去处理,任务处理完后,空闲的线程不会被回收

8.2 大于corePoolSize且小于等于maximunmPoolSize

启动新的线程去处理,任务处理完后,空闲的线程不会被回收。
这里要注意,如果阻塞队列采用无界LinkedBlockingQueue,因为超过corePoolSize的线程会被存放到阻塞队列中,因此keepAliveTime参数对LinkedBlockingQueue不起作用。

8.3 大于maximunmPoolSize

当线程池中的资源被用完时,线程池对新添加的的任务线程采用不同的处理策略。
这里要注意,如果阻塞队列采用无界LinkedBlockingQueue,因为超过corePoolSize的线程会被存放到阻塞队列中,因此不需要使用拒绝策略。

9 线程池的关闭

9.1 shutdown

调用shutdown后,如果线程池中还有任务,会执行完剩下所有的任务才会停止。
如果新增线程到线程池,则会爆出RejectedExecutionException异常。

9.2 shutdownNow

调用shutdown后,如果线程池中还有任务,也会被中断,并抛出InterruptedException异常。

10 参考文献

1)JDK7在线文档
https://tool.oschina.net/apidocs/apidoc?api=jdk_7u4
2) JDK8在线文档
https://docs.oracle.com/javase/8/docs/api/
3) Bruce Eckel. Java编程思想,第4版,2007,机械工业出版社
4)方腾飞,魏鹏,程晓明. Java并发编程的艺术,第1版,2015年,机械工业出版社

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值