线程池
线程池
自定义线程池
步骤1:自定义拒绝策略接口
@FunctionalInterface // 拒绝策略 interface RejectPolicy<T> { void reject(BlockingQueue<T> queue, T task); }
步骤2:自定义任务队列
class BlockingQueue<T> { // 1. 任务队列 private Deque<T> queue = new ArrayDeque<>(); // 2. 锁 private ReentrantLock lock = new ReentrantLock(); // 3. 生产者条件变量 private Condition fullWaitSet = lock.newCondition(); // 4. 消费者条件变量 private Condition emptyWaitSet = lock.newCondition(); // 5. 容量 private int capcity; public BlockingQueue(int capcity) { this.capcity = capcity; } // 带超时阻塞获取 public T poll(long timeout, TimeUnit unit) { lock.lock(); try { // 将 timeout 统一转换为 纳秒 long nanos = unit.toNanos(timeout); while (queue.isEmpty()) { try { // 返回值是剩余时间 if (nanos <= 0) { return null; } nanos = emptyWaitSet.awaitNanos(nanos); } catch (InterruptedException e) { e.printStackTrace(); } } T t = queue.removeFirst(); fullWaitSet.signal(); return t; } finally { lock.unlock(); } } // 阻塞获取 public T take() { lock.lock(); try { while (queue.isEmpty()) { try { emptyWaitSet.await(); } catch (InterruptedException e) { e.printStackTrace(); } } T t = queue.removeFirst(); fullWaitSet.signal(); return t; } finally { lock.unlock(); } } // 阻塞添加 public void put(T task) { lock.lock(); try { while (queue.size() == capcity) { try { log.debug("等待加入任务队列 {} ...", task); fullWaitSet.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("加入任务队列 {}", task); queue.addLast(task); emptyWaitSet.signal(); } finally { lock.unlock(); } } // 带超时时间阻塞添加 public boolean offer(T task, long timeout, TimeUnit timeUnit) { lock.lock(); try { long nanos = timeUnit.toNanos(timeout); while (queue.size() == capcity) { try { if(nanos <= 0) { return false; } log.debug("等待加入任务队列 {} ...", task); nanos = fullWaitSet.awaitNanos(nanos); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("加入任务队列 {}", task); queue.addLast(task); emptyWaitSet.signal(); return true; } finally { lock.unlock(); } } public int size() { lock.lock(); try { return queue.size(); } finally { lock.unlock(); } } public void tryPut(RejectPolicy<T> rejectPolicy, T task) { lock.lock(); try { // 判断队列是否满 if(queue.size() == capcity) { rejectPolicy.reject(this, task); } else { // 有空闲 log.debug("加入任务队列 {}", task); queue.addLast(task); emptyWaitSet.signal(); } } finally { lock.unlock(); } } }
步骤3:自定义线程池
class ThreadPool { // 任务队列 private BlockingQueue<Runnable> taskQueue; // 线程集合 private HashSet<Worker> workers = new HashSet<>(); // 核心线程数 private int coreSize; // 获取任务时的超时时间 private long timeout; private TimeUnit timeUnit; private RejectPolicy<Runnable> rejectPolicy; // 执行任务 public void execute(Runnable task) { // 当任务数没有超过 coreSize 时,直接交给 worker 对象执行 // 如果任务数超过 coreSize 时,加入任务队列暂存 synchronized (workers) { if (workers.size() < coreSize) { Worker worker = new Worker(task); log.debug("新增 worker{}, {}", worker, task); workers.add(worker); worker.start(); } else { // taskQueue.put(task); // 1) 死等 // 2) 带超时等待 // 3) 让调用者放弃任务执行 // 4) 让调用者抛出异常 // 5) 让调用者自己执行任务 taskQueue.tryPut(rejectPolicy, task); } } } public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity, RejectPolicy<Runnable> rejectPolicy) { this.coreSize = coreSize; this.timeout = timeout; this.timeUnit = timeUnit; this.taskQueue = new BlockingQueue<>(queueCapcity); this.rejectPolicy = rejectPolicy; } class Worker extends Thread { private Runnable task; public Worker(Runnable task) { this.task = task; } @Override public void run() { // 执行任务 // 1) 当 task 不为空,执行任务 // 2) 当 task 执行完毕,再接着从任务队列获取任务并执行 // while(task != null || (task = taskQueue.take()) != null) { while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) { try { log.debug("正在执行...{}", task); task.run(); } catch (Exception e) { e.printStackTrace(); } finally { task = null; } } synchronized (workers) { log.debug("worker 被移除{}", this); workers.remove(this); } } } }
步骤四:测试
public static void main(String[] args) { ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1, (queue, task)->{ // 1. 死等 // queue.put(task); // 2) 带超时等待 // queue.offer(task, 1500, TimeUnit.MILLISECONDS); // 3) 让调用者放弃任务执行 // log.debug("放弃{}", task); // 4) 让调用者抛出异常 // throw new RuntimeException("任务执行失败 " + task); // 5) 让调用者自己执行任务 task.run(); }); for (int i = 0; i < 4; i++) { int j = i; threadPool.execute(() -> { try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("{}", j); }); } }
自己实现的线程池
@Slf4j(topic = "c.PoolTest1") public class PoolTest1 { public static void main(String[] args) { Pool pool = new Pool(2, 1000, TimeUnit.MILLISECONDS, 1, (queue, task) -> { // 死等 // queue.push(task); // 带超时等待 // queue.offer(task, 2000,TimeUnit.MILLISECONDS); // 让调用者放弃任务执行 // log.info("啥都不写,就是放弃{}!!!", task); // 让调用者抛出异常 // throw new RuntimeException("抛出异常,后面的都不能运行"); // 让调用者自己操作 task.run(); }); for (int i = 0; i < 4; i++) { int j = i; pool.execute(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } log.info("{}", j); }); } } } @Slf4j(topic = "c.Pool") class Pool { // 任务队列 private BlockingQueues<Runnable> taskQueue; // 线程集合 private HashSet<Work> workers = new HashSet<>(); // 核心线程数 private int coreSize; // 获取任务的超时时间 private long timeout; // 队列的容量 private TimeUnit timeUnit; // 拒绝策略的接口 private RejectPolicy<Runnable> rejectPolicy; public Pool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapicity, RejectPolicy<Runnable> rejectPolicy) { this.taskQueue = new BlockingQueues<>(queueCapicity); this.coreSize = coreSize; this.timeout = timeout; this.timeUnit = timeUnit; this.rejectPolicy = rejectPolicy; } // 执行任务 public void execute(Runnable task) { // 锁住workers synchronized (workers) { if (workers.size() < coreSize) { // 创建新的工作线程 Work work = new Work(task); log.info("新增worker:{}", work); // 放入工作线程集合 workers.add(work); work.start(); } else { // 如果工作线程不够用,放入阻塞队列中 // 死等 // taskQueue.push(task); taskQueue.tryPut(rejectPolicy, task); } } } class Work extends Thread { // 任务 private Runnable task; public Work(Runnable task) { this.task = task; } @Override public void run() { // 执行任务 // 1.如果任务不为空,直接执行 // 2.如果任务为空,那么从任务队列中获取 // while (task != null || (task = taskQueue.take()) != null) { // 如果没加超时时间,线程会一直等待任务 while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) { try { log.info("正在执行...{}", task); task.run(); } catch (Exception exception) { exception.printStackTrace(); } finally { // 执行完毕,任务为空 task = null; } } synchronized (workers) { log.info("一直没任务,移除线程:{}", this); workers.remove(this); } } } } /** * 阻塞队列 * * @param <T> */ @Slf4j(topic = "c.BlockingQueues") class BlockingQueues<T> { // 线程队列 private Deque<T> quque = new ArrayDeque<>(); // 锁 private ReentrantLock lock = new ReentrantLock(); // 生产者条件变量 private Condition proWaitSet = lock.newCondition(); // 消费者条件变量 private Condition conWaitSet = lock.newCondition(); // 容量 private int capicity; public BlockingQueues(int capicity) { this.capicity = capicity; } // 带超时的阻塞获取 public T poll(long waitTime, TimeUnit timeUnit) { // 先加锁 lock.lock(); try { // 将time统一转化为纳秒 long nanos = timeUnit.toNanos(waitTime); while (quque.isEmpty()) { try { // 等待完毕 if (nanos <= 0) { return null; } // 没有就等待 // 此时重新赋值是为了防止虚假唤醒,唤醒之后,时间就变少了 // awaitNanos方法会返回剩余时间 nanos = conWaitSet.awaitNanos(nanos); } catch (InterruptedException e) { e.printStackTrace(); } } return quque.poll(); } finally { proWaitSet.signal(); lock.unlock(); } } // 阻塞获取 public T take() { // 先加锁 lock.lock(); try { while (quque.isEmpty()) { try { // 没有就等待 conWaitSet.await(); } catch (InterruptedException e) { e.printStackTrace(); } } return quque.poll(); } finally { proWaitSet.signal(); lock.unlock(); } } public void push(T t) { // 先加锁 try { lock.lock(); while (quque.size() == capicity) { try { log.info("任务队列已满,等待加入:{}", t); proWaitSet.await(); } catch (InterruptedException e) { e.printStackTrace(); } } quque.addLast(t); log.info("加入任务队列:{}", t); conWaitSet.signal(); } finally { lock.unlock(); } } /** * 带超时时间的阻塞添加 * * @param task * @param timeout * @param timeUnit * @return */ public boolean offer(T task, int timeout, TimeUnit timeUnit) { // 先加锁 try { lock.lock(); long nanos = timeUnit.toNanos(timeout); while (quque.size() == capicity) { try { if (nanos <= 0) { return false; } log.info("任务队列已满,等待加入:{}", task); nanos = proWaitSet.awaitNanos(nanos); } catch (InterruptedException e) { e.printStackTrace(); } } quque.addLast(task); log.info("加入任务队列:{}", task); conWaitSet.signal(); return true; } finally { lock.unlock(); } } /** * 带有拒接策略的放入任务 * * @param rejectPolicy * @param task */ public void tryPut(RejectPolicy<T> rejectPolicy, T task) { try { lock.lock(); // 队列已满 if (quque.size() == capicity) { // 调用操作者实现的操作 rejectPolicy.reject(this, task); } else { // 还有空闲 quque.addLast(task); log.info("加入任务队列:{}", task); conWaitSet.signal(); } } finally { lock.unlock(); } } public int size() { lock.lock(); try { return quque.size(); } finally { lock.unlock(); } } } /** * 拒接策略 * * @param <T> */ @FunctionalInterface interface RejectPolicy<T> { void reject(BlockingQueues<T> queues, T task); }
ThreadPoolExecutor
线程池状态
ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量
从数字上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING(因为是高三位,第一位是符号所以RUNNING是负数)
这些信息存储在一个原子变量 ctl 中,目的是将线程池状态与线程个数合二为一,这样就可以用一次 cas 原子操作进行赋值。
// c 为旧值, ctlOf 返回结果为新值 ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))); // rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数,ctl 是合并它们 private static int ctlOf(int rs, int wc) { return rs | wc; }
构造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
-
corePoolSize 核心线程数目 (最多保留的线程数)
-
maximumPoolSize 最大线程数目
-
keepAliveTime 生存时间 - 针对救急线程
-
unit 时间单位 - 针对救急线程
-
workQueue 阻塞队列
-
threadFactory 线程工厂 - 可以为线程创建时起个好名字
-
handler 拒绝策略
工作方式:
-
线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
-
当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排队,直到有空闲的线程。
-
如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。
-
如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,其它著名框架也提供了实现
-
-
AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
-
CallerRunsPolicy 让调用者运行任务
-
DiscardPolicy 放弃本次任务
-
DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
-
Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方
-
便定位问题
-
Netty 的实现,是创建一个新线程来执行任务
-
ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
-
PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
-
-
当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由keepAliveTime 和 unit 来控制。
根据这个构造方法,JDK Executors 类中提供了众多工厂方法来创建各种用途的线程池。
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
特点
-
核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着
-
-
全部都是救急线程(60s 后可以回收)
-
救急线程可以无限创建
-
-
队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交货)
SynchronousQueue<Integer> integers = new SynchronousQueue<>(); new Thread(() -> { try { log.debug("putting {} ", 1); integers.put(1); log.debug("{} putted...", 1); log.debug("putting...{} ", 2); integers.put(2); log.debug("{} putted...", 2); } catch (InterruptedException e) { e.printStackTrace(); } },"t1").start(); sleep(1); new Thread(() -> { try { log.debug("taking {}", 1); integers.take(); } catch (InterruptedException e) { e.printStackTrace(); } },"t2").start(); sleep(1); new Thread(() -> { try { log.debug("taking {}", 2); integers.take(); } catch (InterruptedException e) { e.printStackTrace(); } },"t3").start();
输出
11:48:15.500 c.TestSynchronousQueue [t1] - putting 1 11:48:16.500 c.TestSynchronousQueue [t2] - taking 1 11:48:16.500 c.TestSynchronousQueue [t1] - 1 putted... 11:48:16.500 c.TestSynchronousQueue [t1] - putting...2 11:48:17.502 c.TestSynchronousQueue [t3] - taking 2 11:48:17.503 c.TestSynchronousQueue [t1] - 2 putted...
评价 整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。 适合任务数比较密集,但每个任务执行时间较短的情况。
newSingleThreadExecutor
使用场景:
希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。
区别:
-
自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作
-
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
-
-
FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
-
-
Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
-
-
对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
-
提交任务
// 执行任务 void execute(Runnable command); // 提交任务 task,用返回值 Future 获得任务执行结果 <T> Future<T> submit(Callable<T> task); // 提交 tasks 中所有任务 <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException; // 提交 tasks 中所有任务,带超时时间 <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException; // 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消 <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; // 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间 <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
关闭线程
shutdown
/* 线程池状态变为 SHUTDOWN - 不会接收新任务 - 但已提交任务会执行完 - 此方法不会阻塞调用线程的执行 */ void shutdown();
public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); // 修改线程池状态 advanceRunState(SHUTDOWN); // 仅会打断空闲线程 interruptIdleWorkers(); onShutdown(); // 扩展点 ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } // 尝试终结(没有运行的线程可以立刻终结,如果还有运行的线程也不会等) tryTerminate(); }
shutdownNow
/* 线程池状态变为 STOP - 不会接收新任务 - 会将队列中的任务返回 - 并用 interrupt 的方式中断正在执行的任务 */ List<Runnable> shutdownNow();
public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); // 修改线程池状态 advanceRunState(STOP); // 打断所有线程 interruptWorkers(); // 获取队列中剩余任务 tasks = drainQueue(); } finally { mainLock.unlock(); } // 尝试终结 tryTerminate(); return tasks; }
其它方法
// 不在 RUNNING 状态的线程池,此方法就返回 true boolean isShutdown(); // 线程池状态是否是 TERMINATED boolean isTerminated(); // 调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待 boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
模式之 Worker Thread
任务调度线程池
在『任务调度线程池』功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。
public static void main(String[] args) { Timer timer = new Timer(); TimerTask task1 = new TimerTask() { @Override public void run() { log.debug("task 1"); sleep(2); } }; TimerTask task2 = new TimerTask() { @Override public void run() { log.debug("task 2"); } }; // 使用 timer 添加两个任务,希望它们都在 1s 后执行 // 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的延时,影响了『任务2』的执行 timer.schedule(task1, 1000); timer.schedule(task2, 1000); }
输出
20:46:09.444 c.TestTimer [main] - start... 20:46:10.447 c.TestTimer [Timer-0] - task 1 20:46:12.448 c.TestTimer [Timer-0] - task 2
使用 ScheduledExecutorService 改写:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); // 添加两个任务,希望它们都在 1s 后执行 executor.schedule(() -> { System.out.println("任务1,执行时间:" + new Date()); try { Thread.sleep(2000); } catch (InterruptedException e) { } }, 1000, TimeUnit.MILLISECONDS); executor.schedule(() -> { System.out.println("任务2,执行时间:" + new Date()); }, 1000, TimeUnit.MILLISECONDS);
输出
任务1,执行时间:Thu Jan 03 12:45:17 CST 2019 任务2,执行时间:Thu Jan 03 12:45:17 CST 2019
scheduleAtFixedRate 例子:
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); log.debug("start..."); pool.scheduleAtFixedRate(() -> { log.debug("running..."); }, 1, 1, TimeUnit.SECONDS);
输出
21:45:43.167 c.TestTimer [main] - start... 21:45:44.215 c.TestTimer [pool-1-thread-1] - running... 21:45:45.215 c.TestTimer [pool-1-thread-1] - running... 21:45:46.215 c.TestTimer [pool-1-thread-1] - running... 21:45:47.215 c.TestTimer [pool-1-thread-1] - running...
scheduleAtFixedRate 例子(任务执行时间超过了间隔时间):
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); log.debug("start..."); pool.scheduleAtFixedRate(() -> { log.debug("running..."); sleep(2); }, 1, 1, TimeUnit.SECONDS);
输出分析:一开始,延时 1s,接下来,由于任务执行时间 > 间隔时间,间隔被『撑』到了 2s
21:44:30.311 c.TestTimer [main] - start... 21:44:31.360 c.TestTimer [pool-1-thread-1] - running... 21:44:33.361 c.TestTimer [pool-1-thread-1] - running... 21:44:35.362 c.TestTimer [pool-1-thread-1] - running... 21:44:37.362 c.TestTimer [pool-1-thread-1] - running
scheduleWithFixedDelay 例子:
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); log.debug("start..."); pool.scheduleWithFixedDelay(()-> { log.debug("running..."); sleep(2); }, 1, 1, TimeUnit.SECONDS);
输出分析:一开始,延时 1s,scheduleWithFixedDelay 的间隔是 上一个任务结束 <-> 延时 <-> 下一个任务开始 所以间隔都是 3s
21:40:55.078 c.TestTimer [main] - start... 21:40:56.140 c.TestTimer [pool-1-thread-1] - running... 21:40:59.143 c.TestTimer [pool-1-thread-1] - running... 21:41:02.145 c.TestTimer [pool-1-thread-1] - running... 21:41:05.147 c.TestTimer [pool-1-thread-1] - running...
评价 整个线程池表现为:线程数固定,任务数多于线程数时,会放入无界队列排队。任务执行完毕,这些线程也不会被释放。用来执行延迟或反复执行的任务。
正确处理执行任务异常
方法1:主动捉异常
pool.submit(() -> { try { log.debug("task1"); int i = 1 / 0; } catch (Exception e) { log.error("error:", e); } });
输出
21:59:04.558 c.TestTimer [pool-1-thread-1] - task1 21:59:04.562 c.TestTimer [pool-1-thread-1] - error: java.lang.ArithmeticException: / by zero at cn.itcast.n8.TestTimer.lambda$main$0(TestTimer.java:28) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)
方法2:使用 Future
ExecutorService pool = Executors.newFixedThreadPool(1); ExecutorService pool = Executors.newFixedThreadPool(1); Future<Boolean> f = pool.submit(() -> { log.debug("task1"); int i = 1 / 0; return true; }); log.debug("result:{}", f.get());
输出
21:54:58.208 c.TestTimer [pool-1-thread-1] - task1 Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero at java.util.concurrent.FutureTask.report(FutureTask.java:122) at java.util.concurrent.FutureTask.get(FutureTask.java:192) at cn.itcast.n8.TestTimer.main(TestTimer.java:31) Caused by: java.lang.ArithmeticException: / by zero at cn.itcast.n8.TestTimer.lambda$main$0(TestTimer.java:28) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)
JUC
AQS原理
概述
全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架。
特点:
-
用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
-
-
getState - 获取 state 状态
-
-
-
setState - 设置 state 状态
-
-
-
compareAndSetState - cas 机制设置 state 状态
-
-
-
独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
-
-
提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
-
条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet
子类主要实现这样一些方法(默认抛出 UnsupportedOperationException)
-
tryAcquire
-
tryRelease
-
tryAcquireShared
-
tryReleaseShared
-
isHeldExclusively
获取锁的姿势
// 如果获取锁失败 if (!tryAcquire(arg)) { // 入队, 可以选择阻塞当前线程 park unpark }
释放锁的姿势
// 如果释放锁成功 if (tryRelease(arg)) { // 让阻塞线程恢复运行 }
目标
AQS 要实现的功能目标
-
阻塞版本获取锁 acquire 和非阻塞的版本尝试获取锁 tryAcquire
-
获取锁超时机制
-
通过打断取消机制
-
独占机制及共享机制
-
条件不满足时的等待机制
实现不可重入锁
@Slf4j(topic = "c.MyLock") public class MyLock implements Lock { public static void main(String[] args) { MyLock lock = new MyLock(); new Thread(() -> { lock.lock(); try { log.debug("locking..."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } finally { log.debug("unlocking..."); lock.unlock(); } }, "t1").start(); new Thread(() -> { lock.lock(); try { log.debug("locking..."); } finally { log.debug("unlocking..."); lock.unlock(); } }, "t2").start(); } // 独占锁 class MySync extends AbstractQueuedSynchronizer { @Override protected boolean tryAcquire(int acquires) { if (acquires == 1) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } } return false; } @Override protected boolean tryRelease(int acquires) { if (acquires == 1) { if (getState() == 0) { throw new IllegalMonitorStateException(); } setExclusiveOwnerThread(null); setState(0); return true; } return false; } protected Condition newCondition() { return new ConditionObject(); } @Override protected boolean isHeldExclusively() { return getState() == 1; } } private MySync mySync = new MySync(); @Override // 加锁,不成功会进入等待队列 public void lock() { mySync.acquire(1); } @Override // 加锁,可打断 public void lockInterruptibly() throws InterruptedException { mySync.acquireInterruptibly(1); } @Override // 尝试加锁(一次) public boolean tryLock() { return mySync.tryAcquire(1); } @Override // 尝试加锁,带超时 public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return mySync.tryAcquireNanos(1, unit.toNanos(time)); } @Override // 解锁 public void unlock() { mySync.release(1); } @Override // 创建条件变量 public Condition newCondition() { return mySync.newCondition(); } }
设计
AQS 的基本思想其实很简单
获取锁的逻辑
while(state 状态不允许获取) { if(队列中还没有此线程) { 入队并阻塞 } } 当前线程出队
释放锁的逻辑
if(state 状态允许了) { 恢复阻塞的线程(s) }
要点
-
原子维护 state 状态
-
阻塞及恢复线程
-
维护队列
state 设计
-
state 使用 volatile 配合 cas 保证其修改时的原子性
-
state 使用了 32bit int 来维护同步状态,因为当时使用 long 在很多平台下测试的结果并不理想
阻塞恢复设计
-
早期的控制线程暂停和恢复的 api 有 suspend 和 resume,但它们是不可用的,因为如果先调用的 resume 那么 suspend 将感知不到
-
解决方法是使用 park & unpark 来实现线程的暂停和恢复,具体原理在之前讲过了,先 unpark 再 park 也没问题
-
park & unpark 是针对线程的,而不是针对同步器的,因此控制粒度更为精细
-
park 线程还可以通过 interrupt 打断
队列设计
-
使用了 FIFO 先入先出队列,并不支持优先级队列
-
设计时借鉴了 CLH 队列,它是一种单向无锁队列
队列中有 head 和 tail 两个指针节点,都用 volatile 修饰配合 cas 使用,每个节点有 state 维护节点状态。
入队伪代码,只需要考虑 tail 赋值的原子性
do { // 原来的 tail Node prev = tail; // 用 cas 在原来 tail 的基础上改为 node } while(tail.compareAndSet(prev, node))
出队伪代码
// prev 是上一个节点 while((Node prev=node.prev).state != 唤醒状态) { } // 设置头节点 head = node;
CLH 好处:
-
无锁,使用自旋
-
快速,无阻塞
AQS 在一些方面改进了 CLH
private Node enq(final Node node) { for (;;) { Node t = tail; // 队列中还没有元素 tail 为 null if (t == null) { // 将 head 从 null -> dummy if (compareAndSetHead(new Node())) tail = head; } else { // 将 node 的 prev 设置为原来的 tail node.prev = t; // 将 tail 从原来的 tail 设置为 node if (compareAndSetTail(t, node)) { // 原来 tail 的 next 设置为 node t.next = node; return t; } } } }
主要用到AQS的并发工具类
ReentrantLock原理
非公平锁实现原理
加锁解锁流程
先从构造器开始看,默认为非公平锁实现
public ReentrantLock() { sync = new NonfairSync(); }
NonfairSync 继承自 AQS,没有竞争时
第一个竞争出现时
Thread-1 执行了
-
CAS 尝试将 state 由 0 改为 1,结果失败
-
进入 tryAcquire 逻辑,这时 state 已经是1,结果仍然失败
-
接下来进入 addWaiter 逻辑,构造 Node 队列
-
-
图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态
-
-
-
Node 的创建是懒惰的
-
-
-
其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程
-
当前线程进入 acquireQueued 逻辑
-
acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
-
如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
-
进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false(-1表示可以唤醒下一个节点)
-
shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时state 仍为 1,失败
-
当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true
-
进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)
再次有多个线程经历上述过程竞争失败,变成这个样子,都在等待
Thread-0 释放锁,进入 tryRelease 流程,如果成功
-
设置 exclusiveOwnerThread 为 null
-
state = 0
当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程
找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1
回到 Thread-1 的 acquireQueued 流程
如果加锁成功(没有竞争),会设置
-
exclusiveOwnerThread 为 Thread-1,state = 1
-
head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread
-
原本的 head 因为从链表断开,而可被垃圾回收
如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了
如果不巧又被 Thread-4 占了先
-
Thread-4 被设置为 exclusiveOwnerThread,state = 1
-
Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞
加锁源码
// Sync 继承自 AQS static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; // 加锁实现 final void lock() { // 首先用 cas 尝试(仅尝试一次)将 state 从 0 改为 1, 如果成功表示获得了独占锁 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else // 如果尝试失败,进入 ㈠ acquire(1); } // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处 public final void acquire(int arg) { // ㈡ tryAcquire if ( !tryAcquire(arg) && // 当 tryAcquire 返回为 false 时, 先调用 addWaiter ㈣, 接着 acquireQueued ㈤ acquireQueued(addWaiter(Node.EXCLUSIVE), arg) ) { selfInterrupt(); } } // ㈡ 进入 ㈢ protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } // ㈢ Sync 继承过来的方法, 方便阅读, 放在此处 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 如果还没有获得锁 if (c == 0) { // 尝试用 cas 获得, 这里体现了非公平性: 不去检查 AQS 队列 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入 else if (current == getExclusiveOwnerThread()) { // state++ int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } // 获取失败, 回到调用处 return false; } // ㈣ AQS 继承过来的方法, 方便阅读, 放在此处 private Node addWaiter(Node mode) { // 将当前线程关联到一个 Node 对象上, 模式为独占模式 Node node = new Node(Thread.currentThread(), mode); // 如果 tail 不为 null, cas 尝试将 Node 对象加入 AQS 队列尾部 Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { // 双向链表 pred.next = node; return node; } } // 尝试将 Node 加入 AQS, 进入 ㈥ enq(node); return node; } // ㈥ AQS 继承过来的方法, 方便阅读, 放在此处 private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // 还没有, 设置 head 为哨兵节点(不对应线程,状态为 0) if (compareAndSetHead(new Node())) { tail = head; } } else { // cas 尝试将 Node 对象加入 AQS 队列尾部 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } // ㈤ AQS 继承过来的方法, 方便阅读, 放在此处 final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); // 上一个节点是 head, 表示轮到自己(当前线程对应的 node)了, 尝试获取 if (p == head && tryAcquire(arg)) { // 获取成功, 设置自己(当前线程对应的 node)为 head setHead(node); // 上一个节点 help GC p.next = null; failed = false; // 返回中断标记 false return interrupted; } if ( // 判断是否应当 park, 进入 ㈦ shouldParkAfterFailedAcquire(p, node) && // park 等待, 此时 Node 的状态被置为 Node.SIGNAL ㈧ parkAndCheckInterrupt() ) { interrupted = true; } } } finally { if (failed) cancelAcquire(node); } } // ㈦ AQS 继承过来的方法, 方便阅读, 放在此处 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 获取上一个节点的状态 int ws = pred.waitStatus; if (ws == Node.SIGNAL) { // 上一个节点都在阻塞, 那么自己也阻塞好了 return true; } // > 0 表示取消状态 if (ws > 0) { // 上一个节点取消, 那么重构删除前面所有取消的节点, 返回到外层循环重试 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 这次还没有阻塞 // 但下次如果重试不成功, 则需要阻塞,这时需要设置上一个节点状态为 Node.SIGNAL compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } // ㈧ 阻塞当前线程 private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); } }
解锁源码
// Sync 继承自 AQS static final class NonfairSync extends Sync { // 解锁实现 public void unlock() { sync.release(1); } // AQS 继承过来的方法, 方便阅读, 放在此处 public final boolean release(int arg) { // 尝试释放锁, 进入 ㈠ if (tryRelease(arg)) { // 队列头节点 unpark Node h = head; if ( // 队列不为 null h != null && // waitStatus == Node.SIGNAL 才需要 unpark h.waitStatus != 0 ) { // unpark AQS 中等待的线程, 进入 ㈡ unparkSuccessor(h); } return true; } return false; } // ㈠ Sync 继承过来的方法, 方便阅读, 放在此处 protected final boolean tryRelease(int releases) { // state-- int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 支持锁重入, 只有 state 减为 0, 才释放成功 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } // ㈡ AQS 继承过来的方法, 方便阅读, 放在此处 private void unparkSuccessor(Node node) { // 如果状态为 Node.SIGNAL 尝试重置状态为 0 // 不成功也可以 int ws = node.waitStatus; if (ws < 0) { compareAndSetWaitStatus(node, ws, 0); } // 找到需要 unpark 的节点, 但本节点从 AQS 队列中脱离, 是由唤醒节点完成的 Node s = node.next; // 不考虑已取消的节点, 从 AQS 队列从后至前找到队列最前面需要 unpark 的节点 if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread); } }
可重入原理
static final class NonfairSync extends Sync { // ... // Sync 继承过来的方法, 方便阅读, 放在此处 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入 else if (current == getExclusiveOwnerThread()) { // state++ int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } // Sync 继承过来的方法, 方便阅读, 放在此处 protected final boolean tryRelease(int releases) { // state-- int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 支持锁重入, 只有 state 减为 0, 才释放成功 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } }
可打断原理
不可打断模式
在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了
// Sync 继承自 AQS static final class NonfairSync extends Sync { // ... private final boolean parkAndCheckInterrupt() { // 如果打断标记已经是 true, 则 park 会失效 LockSupport.park(this); // interrupted 会清除打断标记 return Thread.interrupted(); } final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (; ; ) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; failed = false; // 还是需要获得锁后, 才能返回打断状态 return interrupted; } if ( shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt() ) { // 如果是因为 interrupt 被唤醒, 返回打断状态为 true interrupted = true; } } } finally { if (failed) cancelAcquire(node); } } public final void acquire(int arg) { if ( !tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg) ) { // 如果打断状态为 true selfInterrupt(); } } static void selfInterrupt() { // 重新产生一次中断 Thread.currentThread().interrupt(); } }
不可打断模式
static final class NonfairSync extends Sync { public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 如果没有获得到锁, 进入 ㈠ if (!tryAcquire(arg)) doAcquireInterruptibly(arg); } // ㈠ 可打断的获取锁流程 private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) { // 在 park 过程中如果被 interrupt 会进入此 // 这时候抛出异常, 而不会再次进入 for (;;) throw new InterruptedException(); } } } finally { if (failed) cancelAcquire(node); } } }
公平锁实现原理
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } // AQS 继承过来的方法, 方便阅读, 放在此处 public final void acquire(int arg) { if ( !tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg) ) { selfInterrupt(); } } // 与非公平锁主要区别在于 tryAcquire 方法的实现 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 先检查 AQS 队列中是否有前驱节点, 没有才去竞争 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处 public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; // h != t 时表示队列中有 Node return h != t && ( // (s = h.next) == null 表示队列中还有没有老二 (s = h.next) == null || // 或者队列中老二线程不是此线程 s.thread != Thread.currentThread() ); } }
条件变量实现原理
每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject
await流程
开始 Thread-0 持有锁,调用 await,进入 ConditionObject 的 addConditionWaiter 流程
创建新的 Node 状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部
接下来进入 AQS 的 fullyRelease 流程,释放同步器上的锁
unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么 Thread-1 竞争成功
park 阻塞 Thread-0
signal流程
假设 Thread-1 要来唤醒 Thread-0
进入 ConditionObject 的 doSignal 流程,取得等待队列中第一个 Node,即 Thread-0 所在 Node
执行 transferForSignal 流程,将该 Node 加入 AQS 队列尾部,将 Thread-0 的 waitStatus 改为 0,Thread-3 的waitStatus 改为 -1
读写锁
概述
ReentrantReadWriteLock
当读操作远远高于写操作时,这时候使用 读写锁 让 读-读 可以并发,提高性能。 类似于数据库中的 select ...from ... lock in share mode
提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法
读写锁的并发问题
-
读锁和读锁可以并发
-
读锁和写锁不能并发
-
写锁和写锁不能并发
注意事项
-
读锁不支持条件变量
-
重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
r.lock(); try { // ... w.lock(); try { // ... } finally { w.unlock(); } } finally { r.unlock(); }
-
重入时降级支持:即持有写锁的情况下去获取读锁
class CachedData { Object data; // 是否有效,如果失效,需要重新计算 data volatile boolean cacheValid; final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { rwl.readLock().lock(); if (!cacheValid) { // 获取写锁前必须释放读锁 rwl.readLock().unlock(); rwl.writeLock().lock(); try { // 判断是否有其它线程已经获取了写锁、更新了缓存, 避免重复更新 if (!cacheValid) { data = ... cacheValid = true; } // 降级为读锁, 释放写锁, 这样能够让其它线程读取缓存 rwl.readLock().lock(); } finally { rwl.writeLock().unlock(); } } // 自己用完数据, 释放读锁 try { use(data); } finally { rwl.readLock().unlock(); } } }
也就是在写锁完毕后,降级为读锁,防止其他线程再写。
Semaphore
概述
信号量,用来限制能同时访问共享资源的线程上限。
可以适用停车场来比较,有车位才能进行停车,如果满了,其他车只能等待,需要等正在停的车走后再能进去停车。也就是可以用来做限流。
测试
public static void main(String[] args) { // 1. 创建 semaphore 对象 Semaphore semaphore = new Semaphore(3); // 2. 10个线程同时运行 for (int i = 0; i < 10; i++) { new Thread(() -> { // 3. 获取许可 try { semaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } try { log.debug("running..."); sleep(1); log.debug("end..."); } finally { // 4. 释放许可 semaphore.release(); } }).start(); } }
结果
07:35:15.485 c.TestSemaphore [Thread-2] - running... 07:35:15.485 c.TestSemaphore [Thread-1] - running... 07:35:15.485 c.TestSemaphore [Thread-0] - running... 07:35:16.490 c.TestSemaphore [Thread-2] - end... 07:35:16.490 c.TestSemaphore [Thread-0] - end... 07:35:16.490 c.TestSemaphore [Thread-1] - end... 07:35:16.490 c.TestSemaphore [Thread-3] - running... 07:35:16.490 c.TestSemaphore [Thread-5] - running... 07:35:16.490 c.TestSemaphore [Thread-4] - running... 07:35:17.490 c.TestSemaphore [Thread-5] - end... 07:35:17.490 c.TestSemaphore [Thread-4] - end... 07:35:17.490 c.TestSemaphore [Thread-3] - end... 07:35:17.490 c.TestSemaphore [Thread-6] - running... 07:35:17.490 c.TestSemaphore [Thread-7] - running... 07:35:17.490 c.TestSemaphore [Thread-9] - running... 07:35:18.491 c.TestSemaphore [Thread-6] - end... 07:35:18.491 c.TestSemaphore [Thread-7] - end... 07:35:18.491 c.TestSemaphore [Thread-9] - end... 07:35:18.491 c.TestSemaphore [Thread-8] - running... 07:35:19.492 c.TestSemaphore [Thread-8] - end...
Semaphore应用
Semaphore原理
加锁解锁流程
Semaphore 有点像一个停车场,permits 就好像停车位数量,当线程获得了 permits 就像是获得了停车位,然后停车场显示空余车位减一
刚开始,permits(state)为 3,这时 5 个线程来获取资源
假设其中 Thread-1,Thread-2,Thread-4 cas 竞争成功,而 Thread-0 和 Thread-3 竞争失败,进入 AQS 队列park 阻塞
这时 Thread-4 释放了 permits,状态如下
接下来 Thread-0 竞争成功,permits 再次设置为 0,设置自己为 head 节点,断开原来的 head 节点,unpark 接下来的 Thread-3 节点,但由于 permits 是 0,因此 Thread-3 在尝试不成功后再次进入 park 状态
源码
static final class NonfairSync extends Sync { private static final long serialVersionUID = -2694183684443567898L; NonfairSync(int permits) { // permits 即 state super(permits); } // Semaphore 方法, 方便阅读, 放在此处 public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); } // AQS 继承过来的方法, 方便阅读, 放在此处 public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } // 尝试获得共享锁 protected int tryAcquireShared(int acquires) { return nonfairTryAcquireShared(acquires); } // Sync 继承过来的方法, 方便阅读, 放在此处 final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if ( // 如果许可已经用完, 返回负数, 表示获取失败, 进入 doAcquireSharedInterruptibly remaining < 0 || // 如果 cas 重试成功, 返回正数, 表示获取成功 compareAndSetState(available, remaining) ) { return remaining; } } } // AQS 继承过来的方法, 方便阅读, 放在此处 private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head) { // 再次尝试获取许可 int r = tryAcquireShared(arg); if (r >= 0) { // 成功后本线程出队(AQS), 所在 Node设置为 head // 如果 head.waitStatus == Node.SIGNAL ==> 0 成功, 下一个节点 unpark // 如果 head.waitStatus == 0 ==> Node.PROPAGATE // r 表示可用资源数, 为 0 则不会继续传播 setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } // 不成功, 设置上一个节点 waitStatus = Node.SIGNAL, 下轮进入 park 阻塞 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } } // Semaphore 方法, 方便阅读, 放在此处 public void release() { sync.releaseShared(1); } // AQS 继承过来的方法, 方便阅读, 放在此处 public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; } // Sync 继承过来的方法, 方便阅读, 放在此处 protected final boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); int next = current + releases; if (next < current) // overflow throw new Error("Maximum permit count exceeded"); if (compareAndSetState(current, next)) return true; } } }
CountdownLatch
概述
用来进行线程同步协作,等待所有线程完成倒计时。
其中构造参数用来初始化等待计数值,await() 用来等待计数归零,countDown() 用来让计数减一
配合线程池使用