并发工具类
CountDownLatch
CountDownLatch 允许一个或多个线程等待其他线程完成操作。
用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。
其实就是阻塞一部分线程让其在达到某个条件之后再执行
CountDownLatch最重要的方法是countDown()和await()
countDown
递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。 如果当前计数等于零,则不发生任何操作。
1 public void countDown() { 2 sync.releaseShared(1); 3 }
sync 委托给了AQS
1 public final boolean releaseShared(int arg) { 2 //调用sync实现的tryReleaseShared 3 if (tryReleaseShared(arg)) { 4 //AQS的释放资源方法 5 doReleaseShared(); 6 return true; 7 } 8 return false; 9 }
tryReleaseShare
1 //syn的方法 2 protected boolean tryReleaseShared(int releases) { 3 //循环进行cas,直到当前线程成功完成cas使计数值(状态值state)减一并更新到state 4 for (;;) { 5 int c = getState(); 6 7 //如果当前状态值为0则直接返回(1) 8 if (c == 0) 9 return false; 10 11 //CAS设置计数值减一(2) 12 int nextc = c-1; 13 if (compareAndSetState(c, nextc)) 14 return nextc == 0; 15 } 16 }
await
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回 true
值。 如果当前计数大于零,则出于线程调度目的,将禁用当前线程.
//CountDownLatch的await()方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
tryAcqiiredShared
//AQS的获取共享资源时候可被中断的方法 public final void acquireSharedInterruptibly(int arg)throws InterruptedException { //如果线程被中断则抛异常 if (Thread.interrupted()) throw new InterruptedException(); //尝试看当前是否计数值为0,为0则直接返回,否者进入AQS的队列等待 if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } //sync类实现的AQS的接口 protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; }
sync调用了AQS的acquireSharedInterruptibly方法,该方法的特点是线程获取资源的时候可以被中断,并且获取到的资源是共享资源
tryAcquireShared方法看当前状态值(计数器值)是否为 0 ,是则当前线程的await()方法直接返回,否则调用AQS的doAcquireSharedInterruptibly让当前线程阻塞。
boolean await(long timeout, TimeUnit unit),当线程调用了 CountDownLatch 对象的该方法后,当前线程会被阻塞,直到下面的情况之一发生才会返回: (1)当所有线程都调用了 CountDownLatch 对象的 countDown 方法后,也就是计时器值为 0 的时候,这时候返回 true; (2) 设置的 timeout 时间到了,因为超时而返回 false; (3)其它线程调用了当前线程的 interrupt()方法中断了当前线程,当前线程会抛出 InterruptedException 异常后返回
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); }
计算每行的数字求和并汇总
1 public class Demo2 { 2 3 private int[] nums; 4 5 public Demo2(int line) { 6 nums = new int[line]; 7 } 8 9 public void calc(String line, int index, CountDownLatch latch) { 10 String[] nus = line.split(","); // 切分出每个值 11 int total = 0; 12 for (String num : nus) { 13 total += Integer.parseInt(num); 14 } 15 nums[index] = total; // 把计算的结果放到数组中指定的位置 16 System.out.println(Thread.currentThread().getName() + " 执行计算任务... " + line + " 结果为:" + total); 17 latch.countDown(); 18 } 19 20 public void sum() { 21 System.out.println("汇总线程开始执行... "); 22 int total = 0; 23 for (int i = 0; i < nums.length; i++) { 24 total += nums[i]; 25 } 26 System.out.println("最终的结果为:" + total); 27 } 28 29 public static void main(String[] args) { 30 31 List<String> contents = readFile(); 32 int lineCount = contents.size(); 33 34 CountDownLatch latch = new CountDownLatch(lineCount); 35 36 Demo2 d = new Demo2(lineCount); 37 for (int i = 0; i < lineCount; i++) { 38 final int j = i; 39 new Thread(new Runnable() { 40 @Override 41 public void run() { 42 d.calc(contents.get(j), j, latch); 43 } 44 }).start(); 45 } 46 47 try { 48 latch.await(); 49 } catch (InterruptedException e) { 50 e.printStackTrace(); 51 } 52 53 d.sum(); 54 } 55 56 private static List<String> readFile() { 57 List<String> contents = new ArrayList<>(); 58 String line = null; 59 BufferedReader br = null; 60 try { 61 br = new BufferedReader(new FileReader("C:\\Users\\11658\\Desktop\\nums.txt")); 62 while ((line = br.readLine()) != null) { 63 contents.add(line); 64 } 65 } catch (Exception e) { 66 e.printStackTrace(); 67 } finally { 68 if (br != null) { 69 try { 70 br.close(); 71 } catch (IOException e) { 72 // TODO Auto-generated catch block 73 e.printStackTrace(); 74 } 75 } 76 } 77 return contents; 78 } 79 80 }
CycliBarrier
允许一组线程全部等待彼此达到共同屏障点的同步辅助。与CountDownLatch不同的是该barrier在释放等待线程后可以重用,所以称它为循环(Cyclic)的屏障(Barrier).
public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierAction; }
变量barrierConmmand也通过构造函数传递而来,这是一个任务,这个任务的执行时机是当所有线程都达到屏障点后。另外CyclicBarrier内部使用独占锁Lock来保证同时只有一个线程调用await方法时候才可以返回,使用lock首先保证了更新计数器count 的原子性,另外使用lock的条件变量 trip 支持了 线程间使用 notify,await 操作进行同步
举例:一起开会
1 public class Demo { 2 3 public void meeting (CyclicBarrier cyclicBarrier){ 4 System.out.println(Thread.currentThread().getName()+"到达会议室等待开会"); 5 try { 6 cyclicBarrier.await(); 7 } catch (Exception e) { 8 e.printStackTrace(); 9 } 10 11 } 12 13 public static void main(String[] args) { 14 Demo d = new Demo(); 15 CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new Runnable() { 16 @Override 17 public void run() { 18 System.out.println("开始开会"); 19 } 20 }); 21 22 for (int i = 0 ; i<10; i++){ 23 new Thread(new Runnable() { 24 @Override 25 public void run() { 26 d.meeting(cyclicBarrier); 27 } 28 }).start(); 29 } 30 31 32 } 33 34 }
Console
Thread-0到达会议室等待开会
Thread-1到达会议室等待开会
......
Thread-8到达会议室等待开会
Thread-9到达会议室等待开会
开始开会
Semaphore
Semaphore(信号量)是用来控制同时访问特定资源的线程数量.
当一个线程想要访问某个共享资源,首先,它必须获得semaphore。如果semaphore的内部计数器的值大于0,那么semaphore减少计数器的值并允许访问共享的资源。计数器的值大于0表示,有可以自由使用的资源,所以线程可以访问并使用它们。
另一种情况,如果semaphore的计数器的值等于0,那么semaphore让线程进入休眠状态一直到计数器大于0。计数器的值等于0表示全部的共享资源都正被线程们使用,所以此线程想要访问就必须等到某个资源成为自由的。
当线程使用完共享资源时,他必须放出semaphore为了让其他线程可以访问共享资源。这个操作会增加semaphore的内部计数器的值
线程可以通过acquire()方法获取到一个许可,然后对共享资源进行操作,注意如果许可集已分配完了,那么线程将进入等待状态,直到其他线程释放许可才有机会再获取许可,线程释放一个许可通过release()方法完成,"许可"将被归还给Semaphore。
举例:
1 public class Demo { 2 3 public void method (Semaphore semaphore) { 4 5 try { 6 semaphore.acquire(); 7 } catch (InterruptedException e) { 8 e.printStackTrace(); 9 } 10 11 System.out.println(Thread.currentThread().getName() + " is run ..."); 12 13 try { 14 Thread.sleep(2000); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 19 semaphore.release(); 20 } 21 22 23 public static void main(String[] args) { 24 25 Demo d = new Demo(); 26 Semaphore semaphore = new Semaphore(10); 27 28 while(true) { 29 30 new Thread(new Runnable() { 31 @Override 32 public void run() { 33 d.method(semaphore); 34 try { 35 Thread.sleep(100); 36 } catch (InterruptedException e) { 37 // TODO Auto-generated catch block 38 e.printStackTrace(); 39 } 40 } 41 }).start(); 42 } 43 44 45 } 46 47 }
小结
在AQS中存在一个变量state,当我们创建Semaphore对象传入许可数值时,最终会赋值给state。state的数值代表同一个时刻可同时操作共享数据的线程数量,每当一个线程请求(如调用Semaphored的acquire()方法)获取同步状态成功,state的值将会减少1,直到state为0时,表示已没有可用的许可数,也就是对共享数据进行操作的线程数已达到最大值,其他后来线程将被阻塞。此时AQS内部会将线程封装成共享模式的Node结点,加入同步队列中等待并开启自旋操作。只有当持有对共享数据访问权限的线程执行完成任务并释放同步状态后,同步队列中的结点线程才有可能获取同步状态并被唤醒执行同步操作。
Exchanger
用于线程间数据的交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
Exchanger类仅可用作两个线程的信息交换,当超过两个线程调用同一个exchanger对象时,得到的结果是随机的
这两个线程通过exchange方法交换数据, 如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
如果两个线程有一个没有到达exchange方法,则会一直等待,如果担心有特殊情况发生,避免一直等待,可以使用exchange(V x, long timeout, TimeUnit unit)设置最大等待时长。
模拟数据比对
1 public class Deno { 2 public void a (Exchanger<String> exchanger){ 3 System.out.println("a 方法执行..."); 4 5 try { 6 Thread.sleep(1000); 7 } catch (InterruptedException e) { 8 e.printStackTrace(); 9 } 10 11 String res = "12345"; 12 try { 13 exchanger.exchange(res); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 } 18 19 public void b(Exchanger<String> exchanger){ 20 System.out.println("b 方法执行..."); 21 try { 22 Thread.sleep(3000); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 27 String res = "12345"; 28 29 try { 30 String value = exchanger.exchange(res); 31 System.out.println("开始进行比对..."); 32 System.out.println("比对结果为:"+value.equals(res)); 33 } catch (InterruptedException e) { 34 e.printStackTrace(); 35 } 36 } 37 38 public static void main(String[] args) { 39 Deno deno = new Deno(); 40 Exchanger<String> exchanger = new Exchanger<>(); 41 42 new Thread(new Runnable() { 43 @Override 44 public void run() { 45 deno.a(exchanger); 46 } 47 }).start(); 48 49 50 new Thread(new Runnable() { 51 @Override 52 public void run() { 53 deno.b(exchanger); 54 } 55 }).start(); 56 } 57 }
Console
a 方法执行...
b 方法执行...
开始进行比对...
比对结果为:true
完