管理线程的资源池,可以复用线程,不用频繁的创建新线程,节省线程开销的资源损耗,节省资源,提高响应速度。主要概念:核心线程、阻塞队列、非核心线程、空闲时间、饱和策略。
大于设定的空闲时间的非核心线程会被销毁释放。
《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executors框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。
ThreadPoolExecutor的构造方法:
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数含义:
corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;
maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;
keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;
unit:keepAliveTime的单位
workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;
threadFactory:线程工厂,用于创建线程,一般用默认即可;
handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务;
四种类型的阻塞队列:
直接提交队列(SynchronousQueue队列)
使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。
有界的任务队列(ArrayBlockingQueue)
使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。
无界的任务队列(LinkedBlockingQueue)
使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题.
优先任务队列(PriorityBlockingQueue)
PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。
待执行的任务实现Comparable接口,返回的值越小,表示优先级越高。
线程池的饱和策略有四种:
通过线程工厂自定义线程创建:
线程池中线程就是通过ThreadPoolExecutor中的ThreadFactory,线程工厂创建的。那么通过自定义ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级等,下面代码我们通过ThreadFactory对线程池中创建的线程进行记录与命名
public class ThreadPool {
private static ExecutorService pool;
public static void main( String[] args )
{
//自定义线程工厂
pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
new ThreadFactory() {
public Thread newThread(Runnable r) {
System.out.println("线程"+r.hashCode()+"创建");
//线程命名
Thread th = new Thread(r,"threadPool"+r.hashCode());
return th;
}
}, new ThreadPoolExecutor.CallerRunsPolicy());
for(int i=0;i<10;i++) {
pool.execute(new ThreadTask());
}
}
}
public class ThreadTask implements Runnable{
public void run() {
//输出执行线程的名称
System.out.println("ThreadName:"+Thread.currentThread().getName());
}
}
ThreadPoolExecutor扩展:
ThreadPoolExecutor扩展主要是围绕beforeExecute()、afterExecute()和terminated()三个接口实现的。
线程吃线程数量的设置没有一个明确的指标,根据实际情况,结合下面这个公式即可
/**
* Nthreads=CPU数量
* Ucpu=目标CPU的使用率,0<=Ucpu<=1
* W/C=任务等待时间与任务计算时间的比率
*/
Nthreads = Ncpu*Ucpu*(1+W/C)
参考文章:ThreadPoolExecutor
继承Thread、实现Runnable接口的多线程实现方式,无法获取到多线程执行的结果。通过实现Callable,返回Future的方式,可以获取到多线程的执行结果。Future表示一个可能还没有完成的异步任务的结果。
get()方法可以当任务结束后返回一个结果,如果调用时,工作还没有结束,则会阻塞线程,直到任务执行完毕
get(long timeout,TimeUnit unit)做多等待timeout的时间就会返回结果
cancel(boolean mayInterruptIfRunning)方法可以用来停止一个任务,如果任务可以停止(通过mayInterruptIfRunning来进行判断),则可以返回true,如果任务已经完成或者已经停止,或者这个任务无法停止,则会返回false.
isDone()方法判断当前方法是否完成
isCancel()方法判断当前方法是否取消
参考文章:CompletableFuture
public static CompletableFuture<Void> runAsync(Runnable runnable) //不支持返回值的异步任务创建,使用默认的ForkJoinPool.commonPool() 的线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)//支持指定线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) //支持返回值
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)//使用原有任务的线程执行回调方法
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action) //回调方法继续交由线程池执行
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn) //异常时执行回调方法
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) //T上一个线程的返回类型,U当前线程的返回类型
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
//example
private static void thenApply() throws Exception {
CompletableFuture<Long> future = CompletableFuture.supplyAsync(new Supplier<Long>() {
@Override
public Long get() {
long result = new Random().nextInt(100);
System.out.println("result1="+result);
return result;
}
}).thenApply(new Function<Long, Long>() {
@Override
public Long apply(Long t) {
long result = t*5;
System.out.println("result2="+result);
return result;
}
});
long result = future.get();
System.out.println(result);
}
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);
thenRun 方法,不关心任务的结果,只要任务完成就开始执行
public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);
CountDownLatch 闭锁,必须等指定的线程数完成后,主线程才能继续向下执行。countDownLatch参考文章
* 初始化:CountDownLatch latch = new CountDownLatch(3);
* 计数器减一:latch.countDown();
* 主线程阻塞等待其他线程完成(计数器降到0,阻塞解除):latch.await();
CyclicBarrier 循环栅栏,当指定数量的线程到达栅栏时,解除线程阻塞,未达到指定数量时,子线程阻塞。CyclicBarrier参考文章
* 初始化指定数量的栅栏和满足条件的处理逻辑:CyclicBarrier barrier = new CyclicBarrier(2,() -> {System.out.println(“[” + Thread.currentThread().getName() + “]” + “task1and2 finish…”);});
* 多个子线程等待栅栏条件触发(有2个await时,自动执行栅栏逻辑): barrier.await();
Exchanger 交换器,两个线程的数据交换,多个偶数线程时,随机配对。线程需同时(指定时间范围内)到达。Exchanger参考文章
* 初始化:Exchanger exchanger = new Exchanger<>()
* 交换数据:Object data = exchanger.exchange(data); //两个线程会互相拿到对方的数据
线程状态参考文章
new : 线程对象被实例化(new)
runable: 线程实例调用start启动,分为等待cpu分配资源(ready)、或正在运行(running)
blocked: 线程等待其他线程释放锁,synchronized 。
waiting:当前线程发起等待(不限期),如Object.wait()。另一个线程通过notify可以唤醒
time_waiting: 有超时时间的等待,和waiting类似,由当前线程发起。Object.wait(time)
terminated: 终止,线程执行完成
Thread.sleep;Object.wait;Condition.await;LockSupport.park;
参考文章
共享锁与独占锁
1. 独占锁:只能有一个线程能获取锁。如reentrantLock。是一种悲观保守的加锁策略
2. 共享锁:允许多个线程同时获取锁,乐观锁,放宽了加锁策略。如ReadWriteLock,允许多个线程进行多操作,读写互斥,写写互斥。aqs的内部类Node 定义了两个常量 SHARED 和 EXCLUSIVE,标识等待线程的锁获取方式。
四种状态的锁:
ConcurrentHashMap 对segment加锁,就是分段锁理念非常好的实践。