[Java多线程]-线程池的基本使用和部分源码解析(创建,执行原理)

时间:2022-06-26 18:06:07

前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)

      多线程爬坑之路-Thread和Runable源码解析

      多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

一.线程池ThreadPool的基本定义?

  线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

二.线程池的背景及作用?

  线程池是用来解决多任务并发,频繁创建线程和销毁线程导致资源的浪费和效率低下的一种技术。类似于数据库连接池,这种池化技术用于将部分资源重复利用,或者说是用已有的资源对多个任务进行服务。线程池就是创建固定的线程数量,服务于并发的任务,避免每一个任务,系统都进行线程的创建和销毁,增加系统的负担,线程数量过多时会造成内存溢出。

三.线程池的运用场景:

  频繁的任务创建,快速的任务响应,同等的任务优先级。如果需要很长时间才有一个任务需要执行,那样创建线程池就是浪费资源,如果执行一个任务需要几个小时,那这个任务在线程池中执行一次就会阻塞大量的线程。任务的优先级在线程池没有作用,因为线程池的任务执行顺序是队列。如果我们需要执行的任务有轻重缓急,那么不建议使用线程池。

四.线程池的基本类图

[Java多线程]-线程池的基本使用和部分源码解析(创建,执行原理)

五.线程池的创建过程:

线程池的类图中我们可以得到最终的两个实现类,ThreadPoolExeutorScheduleThreadPoolExeutor,这两个类都可以创建一个线程池。但是通常我们使用这两个类的封装类Exeutors来创建一个线程池。

1、TheadPoolExeutor:运用这个类我们需要传入四个参数

  • corePoolsize:核心线程数量
  • maximumPoolSize:最大线程数量
  • keepAliveTime:最大存活时间(当一个线程没有任务执行时最大的存活时间)
  • until:用于格式化时间,指定时间类型。TimeUnit:一个枚举类,详情请看:http://www.importnew.com/7219.html
  • workQueue:任务队列的接口声明(BlockingQueue<Runnable>是一个继承Queue的接口)
1 public ThreadPoolExecutor(int corePoolSize,
2                               int maximumPoolSize,
3                               long keepAliveTime,
4                               TimeUnit unit,
5                               BlockingQueue<Runnable> workQueue) {
6         this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
7              Executors.defaultThreadFactory(), defaultHandler);
8     }

2、SheduleThreadPoolExeutor:这个类的四个构造函数可以根据不同的参数构造不同用途的线程池。

 public ScheduledThreadPoolExecutor(int corePoolSize) {
         super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, //调用的是super()方法来构造线程池。
               new DelayedWorkQueue());
     }
   public ScheduledThreadPoolExecutor(int corePoolSize,
                                        RejectedExecutionHandler handler) {
         super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
               new DelayedWorkQueue(), handler);
     }
  public ScheduledThreadPoolExecutor(int corePoolSize,
                                        ThreadFactory threadFactory) {
         super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
               new DelayedWorkQueue(), threadFactory);
     }
 public ScheduledThreadPoolExecutor(int corePoolSize,
                                       RejectedExecutionHandler handler) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), handler);
    }

3、Exeutors:这个类主要是对以上两个类做了包装,通过这个类各种静态方法我们可以创建满足各种需求的线程池。Exeutors类通过传递不同的参数以达到创建不同类型线程池的目的。

Exeutors的方法摘要列表:(省略了部分)完整的类大家可以看源码,API:http://www.javaweb.cc/help/JavaAPI1.6/java/util/concurrent/Executors.html

方法摘要
static Callable<Object> callable(PrivilegedAction<?> action) 
          返回 Callable 对象,调用它时可运行给定特权的操作并返回其结果。
static Callable<Object> callable(PrivilegedExceptionAction<?> action) 
          返回 Callable 对象,调用它时可运行给定特权的异常操作并返回其结果。
static Callable<Object> callable(Runnable task) 
          返回 Callable 对象,调用它时可运行给定的任务并返回 null
static
<T> Callable<T>
callable(Runnable task, T result) 
          返回 Callable 对象,调用它时可运行给定的任务并返回给定的结果。
static ThreadFactory defaultThreadFactory() 
          返回用于创建新线程的默认线程工厂。
static ExecutorService newCachedThreadPool() 
          创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) 
          创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程。
static ExecutorService newFixedThreadPool(int nThreads) 
          创建一个可重用固定线程数的线程池,以共享的*队列方式来运行这些线程。
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 
          创建一个可重用固定线程数的线程池,以共享的*队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程。
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
          创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) 
          创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
static ExecutorService newSingleThreadExecutor() 
          创建一个使用单个 worker 线程的 Executor,以*队列方式来运行该线程。
static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) 
          创建一个使用单个 worker 线程的 Executor,以*队列方式来运行该线程,并在需要时使用提供的 ThreadFactory 创建新线程。
static ScheduledExecutorService newSingleThreadScheduledExecutor() 
          创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) 
          创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
static
<T> Callable<T>
privilegedCallable(Callable<T> callable) 
          返回 Callable 对象,调用它时可在当前的访问控制上下文中执行给定的 callable 对象。
static
<T> Callable<T>
privilegedCallableUsingCurrentClassLoader(Callable<T> callable) 
          返回 Callable 对象,调用它时可在当前的访问控制上下文中,使用当前上下文类加载器作为上下文类加载器来执行给定的 callable 对象。
static ThreadFactory privilegedThreadFactory() 
          返回用于创建新线程的线程工厂,这些新线程与当前线程具有相同的权限。
static ExecutorService unconfigurableExecutorService(ExecutorService executor) 
          返回一个将所有已定义的 ExecutorService 方法委托给指定执行程序的对象,但是使用强制转换可能无法访问其他方法。
static ScheduledExecutorService unconfigurableScheduledExecutorService(ScheduledExecutorService executor) 
          返回一个将所有已定义的 ExecutorService 方法委托给指定执行程序的对象,但是使用强制转换可能无法访问其他方法。

列表中红色部分就是一些常用的创建线程池的方法,这些方法的类型分为两类scheduleExecutorService和ExecutorService,这是两个接口,

scheduleExecutorService类型的线程池分两类:

  1.newScheduleThreadPool:是有延迟执行或者定期执行的任务的线程池

  2.newSingleThreadScheduleExecutor:是有延迟执行或者定期执行的任务的单线程线程池

ExecutorService类型的线程池分三类:

  1.newCacheThreadPool:可重用,可扩展(按需创建)

  2.newFixedThreadPool:可重用,固定数量,共享的*队列

  3.newSingleThreadExeutor:单线程,*队列                      

Exeutors可通过调用上面五种封装方法创建不同应用场景的线程池,这些方法的具体实现源码如下:

1、newCachedThreadPool源码:

  • 调用ThreadPoolExecutor创建线程:
    • 第一个参数为0表示他的核心线程数是0,可根据任务需要创建线程,每一个使用完得线程具有60秒的存活时间,
    • 第二个参数最大线程数:Integer.MAX_VALUE,int类型的最大值。
    • 第三个参数最大存活时间:60秒,第四个参数:时间的单位是SECONDS,秒。
    • 最后一个表示任务队列:SynchronousQueue(这个类的实现后面再看),这里没有指定线程工厂在ThreadPoolExecutor类中会使用默认的工厂。
 1 /**
 2      * Creates a thread pool that creates new threads as needed, but
 3      * will reuse previously constructed threads when they are
 4      * available.  These pools will typically improve the performance
 5      * of programs that execute many short-lived asynchronous tasks.
 6      * Calls to {@code execute} will reuse previously constructed
 7      * threads if available. If no existing thread is available, a new
 8      * thread will be created and added to the pool. Threads that have
 9      * not been used for sixty seconds are terminated and removed from
10      * the cache. Thus, a pool that remains idle for long enough will
11      * not consume any resources. Note that pools with similar
12      * properties but different details (for example, timeout parameters)
13      * may be created using {@link ThreadPoolExecutor} constructors.
14      *
15      * @return the newly created thread pool
16      */
17     public static ExecutorService newCachedThreadPool() {
18         return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
19                                       60L, TimeUnit.SECONDS,
20                                       new SynchronousQueue<Runnable>());
21     }
  • 有线程工厂作为参数:与上面唯一不同的是多了一个参数,threadFactory会替代掉默认的工厂来创建线程。
 1  /**
 2      * Creates a thread pool that creates new threads as needed, but
 3      * will reuse previously constructed threads when they are
 4      * available, and uses the provided
 5      * ThreadFactory to create new threads when needed.
 6      * @param threadFactory the factory to use when creating new threads
 7      * @return the newly created thread pool
 8      * @throws NullPointerException if threadFactory is null
 9      */
10     public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
11         return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
12                                       60L, TimeUnit.SECONDS,
13                                       new SynchronousQueue<Runnable>(),
14                                       threadFactory);
15     }

其他的类我就不一一列出源码了,给出下面的表格,可以一目了然的看到他们之间的创建差异。(其中忽略掉线程工厂Threadfactory和Hanler(对于无法通过一个线程池执行任务的处理程序))因为他们无法对线程池的差异造成影响。

静态方法中的参数主要有三个:ThreadFactor,corePoolsize,Handler.其中corePoolSize最为重要,设置的数量要根据具体的需要而定。过少会造成线程阻塞性能下降,过多会造成资源浪费。

Executors

     静态方法       

          调用方法(new)      

                                                                调用方法Executor的参数

corePoolSize

 maximumPoolSize

keepAliveTime

 TimeUnit   

    workQueue

newCacheThreadPool

ThreadPoolExecutor

0

Integer.MAX_VALUE 

60s

 SECONDS(秒)      

SynchronousQueue

newFixedThreadPool

ThreadPoolExecutor

n(参数)

n(参数)

0s

 MILLISECONDS

LinkedBlockingQueue

newSingleThreadExecutor

ThreadPoolExecutor

1

1

0s

MILLISECONDS

LinkedBlockingQueue

newSingleThreadScheduledExecutor

ScheduledThreadPoolExecutor

1

Integer.MAX_VALUE 

0s

NANOSECONDS

DelayedWorkQueue

newScheduledThreadPool

ScheduledThreadPoolExecutor

n(参数)

Integer.MAX_VALUE 

0s

NANOSECONDS

DelayedWorkQueue

Integer.MAX_VALUE=int型的最大值,2^32-1=2147483647

4、如何选择创建线程池的类型?

  corePoolSize:保证了最少有多少个存活的线程。如果我们的任务很稳定,一直能够保证最少x个任务需要被执行,那我们可以将这个值设置为x,如果我们的任务有时多有时少,有时候没有,我们可以设置为0,让它自动根据需要创建线程,这就是newCacheThreadPool的方法,因为不确定接下来有没有任务,所以这些线程使用完之后保持60秒的活时间,如果有任务则重用这些线程,没任务则60秒后线程销毁。如果我们的任务中,始终有一个任务需要执行,但是有时会有更多的线程,那我们可以选择newSingleThreadScheduledExecutor,他保证了corePoolSize=1,始终有一个线程等待任务。最大值为Integer.MAX_VALUE。我们可以根据自己的场景需要选择合适的线程池。

核心方法从Executors转移到了ThreadPoolExecutor类和ScheduledThreadPoolExecutor类上。下面深入一下构建一个线程池需要的步骤和准备。理解了他是如何创建一个线程池之后我们可以手动写自己的线程池。

6、ThreadPoolExecutor类视图:

[Java多线程]-线程池的基本使用和部分源码解析(创建,执行原理)[Java多线程]-线程池的基本使用和部分源码解析(创建,执行原理)

[Java多线程]-线程池的基本使用和部分源码解析(创建,执行原理)

总结一下类里面就是三个部分:

  1.构造方法:第一个构造方法部分我们已经研究过了

  2.属性操作:第二个部分属性操作,就是获取我们构造的线程池的一些属性,基本就是构造方法中的参数。

  3.线程池管理:主要还是内部的调用,去对线程的创建和管理,对任务的执行和监控。

线程池一旦创建好了只需要往里面添加任务执行就好了。而我们需要关心的就是创建的线程池性能是否足够,是否可以优化。

示例:创建线程池

1 import java.util.concurrent.ThreadFactory;
2 
3 public class ThreadFactorImpl implements ThreadFactory{//这里自己实现了一个线程工厂,只是为了下面创建线程池做准备,大家可以忽略掉默认的工厂没这么简单。后面给源码
4     public Thread newThread(Runnable r) {
5         return new Thread(r);
6     }
7 }
 1 import java.util.concurrent.ExecutorService;
 2 import java.util.concurrent.Executors;
 3 import java.util.concurrent.ScheduledThreadPoolExecutor;
 4 import java.util.concurrent.SynchronousQueue;
 5 import java.util.concurrent.ThreadPoolExecutor;
 6 import java.util.concurrent.TimeUnit;
 7 
 8 public class TestThreadPool {
 9     private int num;
10 
11     public int test() {
12         return num++;
13     }
14     public static void main(String[] args) {
15         ExecutorService es1 = Executors.newCachedThreadPool();
16         ExecutorService es2 = Executors.newCachedThreadPool(new ThreadFactorImpl());
17         ExecutorService es3 = Executors.newFixedThreadPool(10);
18         ExecutorService es4 = Executors.newFixedThreadPool(10, new ThreadFactorImpl());
19         ExecutorService es5 = Executors.newSingleThreadExecutor();
20         ExecutorService es6 = Executors.newSingleThreadExecutor(new ThreadFactorImpl());
21         ExecutorService es7 = Executors.newScheduledThreadPool(10);
22         ExecutorService es8 = Executors.newScheduledThreadPool(10, new ThreadFactorImpl());
23         ExecutorService es9 = new ScheduledThreadPoolExecutor(10);
24         ExecutorService es10 = new ThreadPoolExecutor(1, 10, 20, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
25     }
26 }

当Executors的构造方法满足不了需求的时候,就需要直接通过原始类ThreadPoolExecutor类和ScheduledThreadPoolExecutor类来传入合适的参数。

defaultThreadFactory默认的线程工厂:

 1 static class DefaultThreadFactory implements ThreadFactory {
 2         private static final AtomicInteger poolNumber = new AtomicInteger(1);
 3         private final ThreadGroup group;
 4         private final AtomicInteger threadNumber = new AtomicInteger(1);
 5         private final String namePrefix;
 6 
 7         DefaultThreadFactory() {
 8             SecurityManager s = System.getSecurityManager();
 9             group = (s != null) ? s.getThreadGroup() :
10                                   Thread.currentThread().getThreadGroup();
11             namePrefix = "pool-" +
12                           poolNumber.getAndIncrement() +
13                          "-thread-";
14         }
15 
16         public Thread newThread(Runnable r) {
17             Thread t = new Thread(group, r,
18                                   namePrefix + threadNumber.getAndIncrement(),
19                                   0);
20             if (t.isDaemon())
21                 t.setDaemon(false);
22             if (t.getPriority() != Thread.NORM_PRIORITY)
23                 t.setPriority(Thread.NORM_PRIORITY);
24             return t;
25         }
26     }

创建了线程池,有了线程工厂,当任务量>线程数的时候,工厂就会创建线程去执行任务。

六、线程池是如何管理和执行任务的?

执行任务只需要调用execute()方法就够了,下面我们创建了四个任务分别给各自的对象num数据+1。然后把任务加入线程池,execute方法实际上是加入工作队列的过程。

示例:execute执行任务

 1 import java.util.concurrent.ExecutorService;
 2 import java.util.concurrent.Executors;
 3 
 4 public class TestThreadPool implements Runnable{
 5     private int num;
 6     public TestThreadPool(int num){
 7         this.num = num;
 8     }
 9     public int getNum() {
10         return num;
11     }
12     public void setNum(int num) {
13         this.num = num;
14     }
15     public void run() {
16         setNum(++this.num);
17     }
18     public static void main(String[] args) {
19         ExecutorService es1 = Executors.newCachedThreadPool();
20         //任务t1,t2,t3,t4
21         TestThreadPool t1 = new TestThreadPool(1);
22         TestThreadPool t2 = new TestThreadPool(2);
23         TestThreadPool t3 = new TestThreadPool(3);
24         TestThreadPool t4 = new TestThreadPool(4);
25         es1.execute(t1);
26         es1.execute(t2);
27         es1.execute(t3);
28         es1.execute(t4);
29         try {
30             Thread.sleep(1000);
31         } catch (InterruptedException e) {
32             e.printStackTrace();
33         }
34         System.out.println(t1.getNum());
35         System.out.println(t2.getNum());
36         System.out.println(t3.getNum());
37         System.out.println(t4.getNum());
38     }
39 }

执行结果如下:跟我们预想的结果相同。

2
3
4
5

execute方法源码:

由于执行任务的线程是内部工厂创建的所以我们无法观测到线程的状态和属性,通过Execute的源码来看一下它的执行过程:(不同的创建方法对应了不同的线程池类,同时也对应了不同的execute方法)下面是ThreadPoolExecutor类的execute方法源码

 1 public void execute(Runnable command) {
 2         if (command == null)
 3             throw new NullPointerException();
 4         /*
 5          * Proceed in 3 steps:
 6          *
 7          * 1. If fewer than corePoolSize threads are running, try to
 8          * start a new thread with the given command as its first
 9          * task.  The call to addWorker atomically checks runState and
10          * workerCount, and so prevents false alarms that would add
11          * threads when it shouldn't, by returning false.
12          *
13          * 2. If a task can be successfully queued, then we still need
14          * to double-check whether we should have added a thread
15          * (because existing ones died since last checking) or that
16          * the pool shut down since entry into this method. So we
17          * recheck state and if necessary roll back the enqueuing if
18          * stopped, or start a new thread if there are none.
19          *
20          * 3. If we cannot queue task, then we try to add a new
21          * thread.  If it fails, we know we are shut down or saturated
22          * and so reject the task.
23          */
24         int c = ctl.get();
25         if (workerCountOf(c) < corePoolSize) {
26             if (addWorker(command, true))
27                 return;
28             c = ctl.get();
29         }
30         if (isRunning(c) && workQueue.offer(command)) {
31             int recheck = ctl.get();
32             if (! isRunning(recheck) && remove(command))
33                 reject(command);
34             else if (workerCountOf(recheck) == 0)
35                 addWorker(null, false);
36         }
37         else if (!addWorker(command, false))
38             reject(command);
39     }

英语好的直接看源码的注释部分,注释中说:

excute的过程主要分三步:

  1. 如果当前正在运行的线程数小于corePoolSize的话,那么尝试使用传入的任务(command)作为第一个任务启动一个新的线程执行。addWorker函数会原子性的检查线程池的runState以及workerCount防止不应该添加的新线程被添加。如果是假警报的话,那么addWorker函数就会返回false,表示添加新线程失败。
  2. 如果一个Task被成功的加入队列了,然后仍然需要重新check一次是否需要重新添加一个线程,因为有可能在上一次检查到这次检查之间,已经存在的线程已经死亡。或者,自从进入这个方法后,线程池已经被关闭(shut down)。所以我们需要重新check状态,并且在必要的时候,如果处于stopped状态,需要重新回滚到队列中,或者如果没有的话,就需要重新启动一个线程。
  3. 如果不能把task加入到队列中,那么就会尝试去添加一个新的线程,如果它失败了,就知道是已经当前线程池是处于shut down或者处于饱和状态,那么就执行拒绝(reject)操作。

  简单的来说:就是检查线程池的状态,看是否能够把任务加入到队列或者是否需要用一个线程去执行它,最终就是,加入队列等待,或者加入队列执行,或者说拒绝(线程池状态问题。。)

  再往下深入,就要到虚拟机的实现机制上的一些东西了包括同步机制,锁,锁优化问题,等等。至少到目前为止,我们知道了线程池的使用和它的重点在哪里,怎样根据需求去创建一个合适的线程池。

  后面的话就要进入并发问题了,到了线程的难点部分。