【高并发系列】18、线程池那些事儿2 - 内部实现

线程池内部实现

1、常用线程池定义

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

2、ThreadPoolExecutor构造方法详解

由以上代码可以看到,常用的几个方法虽然有着完全不同的功能特点,但内部实现都是ThreadPoolExecutor类的封装,下面是ThreadPoolExecutor类的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

其中:

corePoolSize

  • the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set
  • 线程池中线程数量

maximumPoolSize

  • the maximum number of threads to allow in the pool
  • 线程池中最大线程数量

keepAliveTime

  • when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
  • 当线程池数量超过corePoolSize时,多余的空闲线程的存活时间,即超过corePoolSize的空闲线程在多长时间内被销毁;

unit

  • the time unit for the keepAliveTime argument
  • keepAliveTime的单位

workQueue

  • the queue to use for holding tasks before they are executed.  This queue will hold only the Runnable tasks submitted by the execute method.
  • 任务队列,被提交但尚未被执行的任务存放到该队列;

threadFactory

  • the factory to use when the executor creates a new thread
  • 线程工厂,用于创建线程,一般使用默认值;

handler

  • the handler to use when execution is blocked because the thread bounds and queue capacities are reached
  • 拒绝策略;当任务太多来不及处理时如何拒绝任务;

2.1 workQueue

指被提交但未执行的任务队列;是java.util.concurrent.BlockingQueue接口对象,仅用于存放Runnable对象;分类如下:

  • 直接提交的队列 - SynchronousQueue

SynchronousQueue没有容量,每一个插入操作都要等待一个相应的删除操作,反之,每一个删除操作都要等待对应的插入操作;

提交的任务总是被提交给线程执行,如果没有空闲的线程则尝试新建线程,如果已经达到线程最大值则执行拒绝策略;

使用SynchronousQueue队列,通常设置很大的maximumPoolSize值,否则很容易执行拒绝策略;

如newCachedThreadPool()方法中:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
  • 有界的任务队列 - ArrayBlockingQueue

构造方法中需传入队列最大容量值

public ArrayBlockingQueue(int capacity)

当有新任务需要执行时:

  1. )线程池实际线程数小于corePoolSize,则优先创建新线程;
  2. )线程池实际线程数大于corePoolSize,若等待队列未满可以加入,则将新任务加入等待队列;
  3. )若等待队列已满无法加入,并且总线程数不大于maximumPoolSize时,创建新线程执行任务;
  4. )若等待队列已满无法加入,并且总线程数大于maximumPoolSize时,执行拒绝策略;
  • 无界的任务队列 - LinkedBlockingQueue

与有界队列相比,除非系统资源耗尽,否则无界任务队列不存在任务入队失败的情况;

当有新任务需要执行时:

  1. )线程池实际线程数小于corePoolSize,则优先创建新线程;
  2. )线程池实际线程数大于corePoolSize,则将新任务加入等待队列,并且线程数不会增加了;

如newFixedThreadPool()方法中:

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

 如newSingleThreadExecutor()方法中:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
  • 优先任务队列 - PriorityBlockingQueue

可以根据任务自身的优先级顺序控制执行先后顺序;

attention:使用自定义线程池时,要根据具体情况选择合适的并发队列作为任务的缓冲;

3、ThreadPoolExecutor.execute()方法

/**
 * Executes the given task sometime in the future.  The task
 * may execute in a new thread or in an existing pooled thread.
 *
 * If the task cannot be submitted for execution, either because this
 * executor has been shutdown or because its capacity has been reached,
 * the task is handled by the current {@code RejectedExecutionHandler}.
 *
 * @param command the task to execute
 * @throws RejectedExecutionException at discretion of
 *         {@code RejectedExecutionHandler}, if the task
 *         cannot be accepted for execution
 * @throws NullPointerException if {@code command} is null
 */
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);
}

先判断当前线程数是否小于corePoolSize核心线程数(workerCountOf(c) < corePoolSize);

如果小于,则通过addWorker(command, true)直接调度执行;

如果不小于,加入等待队列(workQueue.offer(command));

如果加入等待队列失败(如有界队列到达上限,或者使用了SynchronousQueue类等),则将任务直接提交给线程池执行else if (!addWorker(command, false));

如果提交失败,执行拒绝策略reject(command);

4、拒绝策略

是系统超负荷运行时的补救措施;

JDK内置拒绝策略,都实现了java.util.concurrent.RejectedExecutionHandler接口:

  • AbortPolicy:直接抛出异常,阻止系统正常工作;
  • CallerRunsPolicy:只要线程池未关闭,该策略会直接在调用者线程中,运行当前被丢弃的任务;

  • DiscardOldestPolicy:丢弃未执行任务中最老的任务,就是即将被执行的一个任务,并尝试再次提交当前任务;

  • DiscardPolicy:默默丢弃无法处理的任务,不做任何处理;

如果内置策略无法满足实际应用的需求,可以自己扩展RejectedExecutionHandler接口:

public interface RejectedExecutionHandler {
    /**
     * Method that may be invoked by a {@link ThreadPoolExecutor} when
     * {@link ThreadPoolExecutor#execute execute} cannot accept a
     * task.  This may occur when no more threads or queue slots are
     * available because their bounds would be exceeded, or upon
     * shutdown of the Executor.
     *
     * <p>In the absence of other alternatives, the method may throw
     * an unchecked {@link RejectedExecutionException}, which will be
     * propagated to the caller of {@code execute}.
     *
     * @param r the runnable task requested to be executed
     * @param executor the executor attempting to execute this task
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

示例:核心线程、最大线程数都是5,有界任务队列容量为10,所以最多可以容纳15个线程,多余的走拒绝策略;

public class RejectExecutionHandlerDemo {
	public static class TaskDemo implements Runnable {
		@Override
		public void run() {
			System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName());
			try {
				TimeUnit.MILLISECONDS.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) throws InterruptedException {
		TaskDemo task = new TaskDemo();
		ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(10),
				new RejectedExecutionHandler() {
					@Override
					public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
						System.out.println(r.toString() + " is discard.");
					}
				});
		for (int i = 0; i < 16; i++) {
			es.submit(task);
			TimeUnit.MILLISECONDS.sleep(10);
		}
		es.shutdown();
	}
}

console

1550415828931 : pool-1-thread-1
1550415828943 : pool-1-thread-2
1550415828954 : pool-1-thread-3
1550415828964 : pool-1-thread-4
1550415828975 : pool-1-thread-5
java.util.concurrent.FutureTask@7d4991ad is discard.
1550415829933 : pool-1-thread-1
1550415829943 : pool-1-thread-2
1550415829955 : pool-1-thread-3
1550415829969 : pool-1-thread-4
1550415829978 : pool-1-thread-5
1550415830936 : pool-1-thread-1
1550415830944 : pool-1-thread-2
1550415830959 : pool-1-thread-3
1550415830974 : pool-1-thread-4
1550415830979 : pool-1-thread-5

5、自定义线程工厂 ThreadFactory

java.util.concurrent.ThreadFactory接口用于创建线程;

public interface ThreadFactory {
    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

示例:自定义线程工厂,设置所有线程为守护线程,当main线程退出后,强制注销该线程池;

public class ThreadFactoryDemo {
	public static class TaskDemo implements Runnable {
		@Override
		public void run() {
			System.out.println(System.currentTimeMillis() + " : " + Thread.currentThread().getName());
			try {
				TimeUnit.MILLISECONDS.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) {
		TaskDemo task = new TaskDemo();
		ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),
				new ThreadFactory() {
					@Override
					public Thread newThread(Runnable r) {
						Thread t = new Thread(r);
						t.setDaemon(true);
						System.out.println("create " + t);
						return t;
					}
				});
		for (int i = 0; i < 5; i++) {
			es.submit(task);
		}
	}
}

console

create Thread[Thread-0,5,main]
create Thread[Thread-1,5,main]
create Thread[Thread-2,5,main]
1550416438149 : Thread-1
1550416438149 : Thread-0
create Thread[Thread-3,5,main]
1550416438150 : Thread-2
create Thread[Thread-4,5,main]
1550416438150 : Thread-3
1550416438150 : Thread-4

6、线程池扩展

可以覆写ThreadPoolExecutor中的以下三个方法,来对线程池进行控制:

protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }
protected void terminated() { }
public class ExtThreadPool {
	public static class TaskDemo implements Runnable {
		private String name;

		public TaskDemo(String name) {
			this.name = name;
		}
		@Override
		public void run() {
			System.out.println(Thread.currentThread().getName() + " - " + this.name + " - is running.");
			try {
				TimeUnit.MILLISECONDS.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) throws InterruptedException {
		ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
				new LinkedBlockingQueue<Runnable>()) {
			@Override
			protected void beforeExecute(Thread t, Runnable r) {
				System.out.println(t.getName() + " - " + ((TaskDemo) r).name + " - beforeExecute");
			}
			@Override
			protected void afterExecute(Runnable r, Throwable t) {
				System.out.println(Thread.currentThread().getName() + " - " + ((TaskDemo) r).name + " - afterExecute");
			}
			@Override
			protected void terminated() {
				System.out.println("ThreadPool is terminated.");
			}
		};
		for (int i = 0; i < 5; i++) {
			TaskDemo task = new TaskDemo("TASK-" + i);
			es.execute(task);
			TimeUnit.MILLISECONDS.sleep(10);
		}
		es.shutdown();
	}
}

console

pool-1-thread-1 - TASK-0 - beforeExecute
pool-1-thread-1 - TASK-0 - is running.
pool-1-thread-2 - TASK-1 - beforeExecute
pool-1-thread-2 - TASK-1 - is running.
pool-1-thread-3 - TASK-2 - beforeExecute
pool-1-thread-3 - TASK-2 - is running.
pool-1-thread-4 - TASK-3 - beforeExecute
pool-1-thread-4 - TASK-3 - is running.
pool-1-thread-5 - TASK-4 - beforeExecute
pool-1-thread-5 - TASK-4 - is running.
pool-1-thread-1 - TASK-0 - afterExecute
pool-1-thread-2 - TASK-1 - afterExecute
pool-1-thread-3 - TASK-2 - afterExecute
pool-1-thread-4 - TASK-3 - afterExecute
pool-1-thread-5 - TASK-4 - afterExecute
ThreadPool is terminated.

7、优化线程池线程数量

Ncpu = CPU数量
Ucpu = 目标CPU使用率, 0<=Ucpu<=1
W/C = 等待时间与计算时间的比率

为保持处理器达到期望的使用率,最优的线程池大小为

Nthreads = Ncpu * Ucpu * (1 + W/C)

在Java中,通过如下代码获取可用CPU数量:

Runtime.getRuntime().availableProcessors()

8、打印线程池中的错误堆栈信息

public class TraceErrorDemo {
	public static class DivTask implements Runnable {
		private int a, b;
		public DivTask(int a, int b) {
			this.a = a;
			this.b = b;
		}
		@Override
		public void run() {
			double result = a / b;
			System.out.println(result);
		}

	}
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ThreadPoolExecutor pools = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0L, TimeUnit.MILLISECONDS,
				new SynchronousQueue<>());
		for (int i = 0; i < 5; i++) {
			pools.submit(new DivTask(100, i));
		}
	}
}

console

100.0
25.0
33.0
50.0

只有4个输出,把i=0的情况漏掉了,0不能作为除数,但没有任何日志,没有错误提示,好像程序正常执行了一样;

找回部分异常信息:

1、放弃submit()方法,改用execute()方法;

pools.execute(new DivTask(100, i));

2、使用Future接收submit()方法返回值;

Future result = pools.submit(new DivTask(100, i));
result.get();

得到部分异常堆栈信息如下:

Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
	at com.threadpool.TraceErrorDemo$DivTask.run(TraceErrorDemo.java:18)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:748)
100.0
25.0
33.0
50.0

但是还是不知道任务是在哪里提交的,提交位置已经被线程池淹没了,所以自己动手扩展ThreadPoolExecutor线程池,在调度任务之前先保存一下提交任务线程的堆栈信息:

public class TraceThreadPoolExecutor extends ThreadPoolExecutor {
	public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
			BlockingQueue<Runnable> workQueue) {
		super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
	}
	@Override
	public void execute(Runnable command) {
		super.execute(wrap(command, clientTrace(), Thread.currentThread().getName()));
	}
	@Override
	public Future<?> submit(Runnable command) {
		return super.submit(wrap(command, clientTrace(), Thread.currentThread().getName()));
	}
	private Runnable wrap(final Runnable task, final Exception clientStack, String clientThreadName) {
		return new Runnable() {
			@Override
			public void run() {
				try {
					task.run();
				} catch (Exception e) {
					clientStack.printStackTrace();
					throw e;
				}
			}
		};
	}
	private Exception clientTrace() {
		return new Exception("Client stack trace");
	}
	public static class DivTask implements Runnable {
		private int a, b;
		public DivTask(int a, int b) {
			this.a = a;
			this.b = b;
		}
		@Override
		public void run() {
			double result = a / b;
			System.out.println(result);
		}
	}
	public static void main(String[] args) {
		ThreadPoolExecutor pools = new TraceThreadPoolExecutor(0, Integer.MAX_VALUE, 0L, TimeUnit.MILLISECONDS,
				new SynchronousQueue<>());
		for (int i = 0; i < 5; i++) {
			pools.execute(new DivTask(100, i));
		}
	}
}

console

java.lang.Exception: Client stack trace
	at com.threadpool.TraceThreadPoolExecutor.clientTrace(TraceThreadPoolExecutor.java:36)
	at com.threadpool.TraceThreadPoolExecutor.execute(TraceThreadPoolExecutor.java:16)
	at com.threadpool.TraceThreadPoolExecutor.main(TraceThreadPoolExecutor.java:54)
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
	at com.threadpool.TraceThreadPoolExecutor$DivTask.run(TraceThreadPoolExecutor.java:46)
	at com.threadpool.TraceThreadPoolExecutor$1.run(TraceThreadPoolExecutor.java:27)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:748)
100.0
25.0
33.0
50.0

9、Fork/Join框架

“分而治之”的思想体现;

ForkJoinPool线程池对fork/join框架支持的接口

public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task)

ForkJoinTask有两个重要的子类:RecursiveAction和RecursiveTask,分别表示没有返回值和可以携带返回值的任务;

public abstract class RecursiveAction extends ForkJoinTask<Void>
public abstract class RecursiveTask<V> extends ForkJoinTask<V>

示例:计算数列求和

public class CountTask extends RecursiveTask<Long> {
	private static final long serialVersionUID = 8000196622093620501L;
	// 任务分解规模
	private static final int THRESHOLD = 10000;
	private long start;
	private long end;
	public CountTask(long start, long end) {
		this.start = start;
		this.end = end;
	}
	@Override
	protected Long compute() {
		long sum = 0;
		boolean canCompute = (end - start) < THRESHOLD;
		if (canCompute) {
			for (long i = start; i <= end; i++) {
				sum += i;
			}
		} else {
			// 任务再次分解
			long step = (start + end) / 100;
			List<CountTask> subTasks = new ArrayList<>();
			long pos = start;
			for (int i = 0; i < 100; i++) {
				long lastOne = pos + step;
				if (lastOne > end) {
					lastOne = end;
				}
				CountTask subTask = new CountTask(pos, lastOne);
				pos += step + 1;
				subTasks.add(subTask);
				// 提交子任务
				subTask.fork();
			}
			// 等待所有子任务结束,并将结果再次求和
			for (CountTask t : subTasks) {
				sum += t.join();
			}
		}
		return sum;
	}
	public static void main(String[] args) {
		ForkJoinPool forkJoinPool = new ForkJoinPool();
		CountTask task = new CountTask(0, 200000L);
		ForkJoinTask<Long> result = forkJoinPool.submit(task);
		try {
			long res = result.get();
			System.out.println(res);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}
}

console

20000100000

10、Guava中对线程池的扩展

10.1 DirectExecutor

MoreExecutors中的DirectExecutor,将任务在当前线程中直接执行;

使用统一的编码风格来处理同步和异步调用,简化设计:

public class DirectExecutorDemo {
	public static void main(String[] args) {
		Executor exec = MoreExecutors.directExecutor();
		exec.execute(() -> System.out.println("I am runnint in " + Thread.currentThread().getName() + "."));
	}
}

console

I am runnint in main.

10.2 将普通线程池转为Daemon线程池

使用MoreExecutors中的如下方法:

public static ExecutorService getExitingExecutorService(ThreadPoolExecutor executor)
public class DaemonThreadPoolDemo {
	public static void main(String[] args) {
		ThreadPoolExecutor exec = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
		// 如果不设置,程序不会退出
		MoreExecutors.getExitingExecutorService(exec);
		exec.execute(() -> System.out.println("I am running in " + Thread.currentThread().getName() + "."));
	}
}

猜你喜欢

转载自blog.csdn.net/hellboy0621/article/details/87535018