Java并发编程(四)Java线程池相关问题

一、线程池的实现

        如果我们每次使用一个线程就去创建一个线程,使用起来非常方便,但是频繁创建线程会大大降低系统效率,因为创建和销毁线程都需要时间。在java中,我们可以通过线程池来实现线程的复用。ThreadPoolExecutor是线程池中最核心的一个类。

 // 构造方法1
 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }	
 // 构造方法2
 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
	// 构造方法3
	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
	// 构造方法4
	 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

ThreadPoolExecutor一共有4个构造方法,但是从代码可以看出来,前三个构造方法都是调用最后一个构造方法进行的初始化工作。

1.1、各个参数的含义

corePoolSize:核心池的大小。
在创建了线程池之后,默认情况下,线程池中并没有任何线程,而是等待有任务来了才去创建线程。除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,这两个方法是预先创建线程的,即在没有任务来临之前,就先创建了corePoolSize个线程或者一个线程。但是默认情况下,线程池的线程数为0,当有任务到来时,才创建线程执行任务,当线程池中的数量达到了corePoolSize的时候,就把接下来的线程放入缓存队列。

maximumPoolSize:线程池最大线程数。它表示线程池中最多能创建多少线程。

keepAliveTime:表示线程没有任务执行时最多保持多长时间会终止。默认情况下,只有线程池的线程数量大于corePoolSize的时候,keepAliveTime才会起作用,如果一个线程的空闲时间达到了keepAliveTime,则会终止,直到线程数不大于corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,就算线程池中的线程数不大于corePoolSize,keepAliveTime参数也会起作用。

unit:keepAliveTime的时间单位。有7种取值。
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒

workQueue:一个阻塞队列,用来存储等待执行的任务。一般来说,这个参数有以下几种选择。

  • ArrayBlockingQueue:是一个基于数组的有序阻塞队列,此队列按照FIFO(先进先出)原则对元素进行排列。
  • LinkedBlockingQueue:一个基于链表的有序阻塞队列,此队列按照FIFO(先进先出)排序,吞吐量通常要高于ArrayBlockingQueue。
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。吞吐量要高于LinkedBlockingQueue。

threadFactory:线程工厂。主要用来创建线程
handler:表示拒绝处理任务时的策略,当运行线程数已经到达maximumPoolSize时,队列也已经装满时,会调用该参数值拒绝任务。默认情况是AbortPolicy。
有以下四种取值:

	AbortPolicy:直接抛出异常。
	CallerRunsPolicy:只用调用者所在线程来执行任务。
	DiscardOldestPolicy:丢弃队列中最近的一个任务,并执行当前任务。
	DiscardPolicy:不处理,丢弃掉。
1.2、ThreadPoolExecutor的继承关系

public class ThreadPoolExecutor extends AbstractExecutorService { … }
public abstract class AbstractExecutorService implements ExecutorService { … }
public interface ExecutorService extends Executor { … }
public interface Executor { … }

1.3、ThreadPoolExecutor的状态变量
  private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    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;

    // Packing and unpacking ctl
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }

其中ctl是ThreadPoolExecutor的状态变量。

workerCountOf():方法取得当前线程池的线程数量,算法是将ctl的值取低29位。
runStateOf():方法取得线程池的状态,算法是将ctl的值取高3位。
RUNNING 111:表示正在运行
SHUTDOWN 000:表示拒绝接收新的任务。
STOP 001:表示拒绝接收新的任务,并且不再处理任务队列中剩余的任务,还要中断正在执行的任务
TIDYING 010:表示所有线程已经停止,准备执行terminated()方法。
TERMINATED 011:表示已经执行完terminated()方法。
1.4、任务的执行:execute()方法
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
		// 第一种情况
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
		// 第二种情况
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
		// 第三种情况
        else if (!addWorker(command, false))
            reject(command);
    }

以上三种情况分别对应以下:

  • 1.线程池的线程数量小于corePoolSize核心线程任务数量,开启核心线程执行任务。
  • 2.线程池的线程数量不小于corePoolSize核心线程数量,或者开启核心线程失败,尝试将线程以非阻塞的方式添加到任务队列。
  • 3.任务队列已满导致添加任务失败,开启新的非核心线程任务。
    在这里插入图片描述
    在这里插入图片描述
二、常用的四种线程池
2.1、newFixedThreadPool

一个固定线程数量的线程池。可控制线程最大并发数,超出的线程会在队列中等待。

public static ExecutorService newFixedThreadPool(int nThreads) {
		// corePoolSize和maximumPoolSize大小一样,同时传入一个无界阻塞队列,
		// 该线程池的线程数会维持在指定线程数,不会进行回收(根据worker逻辑分析)
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
2.2、newCachedThreadPool

不固定线程数量,且支持最大为Integer.MAX_VALUE的线程数量。

public static ExecutorService newCachedThreadPool() {
		// corePoolSize为0,maximumPoolSize为Integer.MAX_VALUE
		// 就是说来一个任务就创建一个worker,回收时间是60s
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

可缓存线程池:

  • 1.线程数无限制
  • 2.有空闲线程则复用空闲线程,没有就创建新线程
  • 3.一定程度减少频繁创建/销毁线程,减少系统开销
2.3、newSingleThreadExecutor

可以理解为线程数为1的FixedThreadPool。

 public static ExecutorService newSingleThreadExecutor() {
		// corePoolSize和maximumPoolSize大小都为1,
		// 线程池中只有一个线程在运行,其他的都放入阻塞队列。
		// 外面包装的FinalizableDelegatedExecutorService类实现了finalize方法,
		// 在JVM垃圾回收的时候会关闭线程池。
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

单线程化的线程池:

  • 1.有且只有一个工作线程执行任务
  • 2.所有任务按照指定顺序执行,遵循队列的先入先出
2.4、newScheduledThreadPool

支持定时以指定周期循环执行任务。前三种线程池是ThreadPoolExecutor不同配置的实例,最后一种是ScheduledThreadPoolExecutor的实例。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
三、线程池的线程是怎么创建的?是一开始就随着线程池的启动创建的吗?

不是的。线程池默认初始化之后不启动Worker,等待有请求时才启动。
每当我们调用excute()方法添加一个任务时,线程池会做以下判断。

  • 1、首先判断核心线程池是否已满。如果正在运行的线程个数小鱼核心线程池的数量,那么直接创建线程运行这个任务。
  • 2、如果核心线程池已满,那么将这个任务放入队列。
  • 3、如果队列满了,而且正在运行的线程数量小于maximum pool size(最大线程数),那么就创建非核心线程立刻运行这个任务。
  • 4、如果队列满了,而且正在运行的线程数量大于或等于maximum pool size(最大线程数),那么线程池就会抛出异常Reject Execution Exception。当一个线程完成任务时,会从一个队列中取下一个任务来执行。如果一个线程无事可做,超过一定得时间会停掉这个线程。
四、如何向Java线程池中提交线程/任务?

线程池中常用的提交任务/线程的方法有两种:

4.1 execute()

execute()方法接收一个Runnable类型的参数,无返回值。(上面1.4已经分析过了)

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
4.2 submit()方法

submit方法有返回值。线程会返回一个Future类型的对象,通过这个Future可以判断任务是否执行成功,并且可以通过Future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)则会阻塞当前线程一段时间后立即返回,这时候有可能任务还没有执行完。

future的get方法未获得返回值之前会一直阻塞,可以使用future的isDone方法判断任务是否执行完成,然后再决定是否get。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
jstack生成的Thread Dump日志.docx 系统线程状态 (Native Thread Status) 系统线程有如下状态: deadlock 死锁线程,一般指多个线程调用期间进入了相互资源占用,导致一直等待无法释放的情况。 runnable 一般指该线程正在执行状态中,该线程占用了资源,正在处理某个操作,如通过SQL语句查询数据库、对某个文件进行写入等。 blocked 线程正处于阻塞状态,指当前线程执行过程中,所需要的资源长时间等待却一直未能获取到,被容器的线程管理器标识为阻塞状态,可以理解为等待资源超时的线程。 waiting on condition 线程正处于等待资源或等待某个条件的发生,具体的原因需要结合下面堆栈信息进行分析。 (1)如果堆栈信息明确是应用代码,则证明该线程正在等待资源,一般是大量读取某种资源且该资源采用了资源锁的情况下,线程进入等待状态,等待资源的读取,或者正在等待其他线程的执行等。 (2)如果发现有大量的线程都正处于这种状态,并且堆栈信息中得知正等待网络读写,这是因为网络阻塞导致线程无法执行,很有可能是一个网络瓶颈的征兆: 网络非常繁忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读写; 网络可能是空闲的,但由于路由或防火墙等原因,导致包无法正常到达; 所以一定要结合系统的一些性能观察工具进行综合分析,比如netstat统计单位时间的发送包的数量,看是否很明显超过了所在网络带宽的限制;观察CPU的利用率,看系统态的CPU时间是否明显大于用户态的CPU时间。这些都指向由于网络带宽所限导致的网络瓶颈。 (3)还有一种常见的情况是该线程在 sleep,等待 sleep 的时间到了,将被唤醒。 waiting for monitor entry 或 in Object.wait() Moniter 是Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者class的锁,每个对象都有,也仅有一个 Monitor。 从上图可以看出,每个Monitor在某个时刻只能被一个线程拥有,该线程就是 "Active Thread",而其他线程都是 "Waiting Thread",分别在两个队列 "Entry Set"和"Waint Set"里面等待。其中在 "Entry Set" 中等待的线程状态是 waiting for monitor entry,在 "Wait Set" 中等待的线程状态是 in Object.wait()。 (1)"Entry Set"里面的线程。 我们称被 synchronized 保护起来的代码段为临界区,对应的代码如下: synchronized(obj){} 当一个线程申请进入临界区时,它就进入了 "Entry Set" 队列中,这时候有两种可能性: 该Monitor不被其他线程拥有,"Entry Set"里面也没有其他等待的线程。本线程即成为相应类或者对象的Monitor的Owner,执行临界区里面的代码;此时在Thread Dump中显示线程处于 "Runnable" 状态。 该Monitor被其他线程拥有,本线程在 "Entry Set" 队列中等待。此时在Thread Dump中显示线程处于 "waiting for monity entry" 状态。 临界区的设置是为了保证其内部的代码执行的原子性和完整性,但因为临界区在任何时间只允许线程串行通过,这和我们使用多线程的初衷是相反的。如果在多线程程序中大量使用synchronized,或者不适当的使用它,会造成大量线程在临界区的入口等待,造成系统的性能大幅下降。如果在Thread Dump中发现这个情况,应该审视源码并对其进行改进。 (2)"Wait Set"里面的线程 当线程获得了Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(通常是被synchronized的对象)的wait()方法,放弃Monitor,进入 "Wait Set"队列。只有当别的线程在该对象上调用了 notify()或者notifyAll()方法,"Wait Set"队列中的线程才得到机会去竞争,但是只有一个线程获得对象的Monitor,恢复到运行态。"Wait Set"中的线程在Thread Dump中显示的状态为 in Object.wait()。通常来说, 通常来说,当CPU很忙的时候关注 Runnable 状态的线程,反之则关注 waiting for monitor entry 状态的线程。 JVM线程运行状态 (JVM Thread Status)

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值