約3.1 CopyOnWriteArrayListと
パブリック クラス CopyOnWriteArrayListとは、<E>は 拡張オブジェクトが 実装して一覧<E>、ランダム・、Cloneableを、直列化を
多くのシナリオでは、読み出し動作は、書き込み動作よりもはるかに大きくてもよいです。実際にロックされます、各読み取りのためのように、読み出し動作は、元のデータは変更されませんので、それは資源の無駄です。私たちは、複数のスレッドが一覧から内部のデータにアクセスできるようにする必要があり、結局、操作が安全であるお読みください。
これは、我々が前の話マルチスレッド部分である ReentrantReadWriteLock
思考の読み書きロックを読み取り、排他的な書き込みを共有している、非常に似て、読んで、相互に排他的な書き込み、相互に排他的な読み書き。JDK提供 CopyOnWriteArrayList
、さらに思考の読み書きロックに比べアナロジーを。パフォーマンスを最大化するために読み出すために、CopyOnWriteArrayList
読み取りが完全にロックされていない、そしてより強力である:それは、書き込み読み出し動作を詰まらせません。あなただけが書き込みと書き込みの同期の間に待機する必要があります。その結果、読み出し動作のパフォーマンスが大幅に改善されます。それはどのようにそれをするのでしょうか?
3.2 CopyOnWriteArrayListとは、どのようにそれを行うには?
CopyOnWriteArrayList
すべてのクラス変数の操作は(など、設定、追加)を達成するために、基になる配列の新しいコピーを作成することです。リストを変更する必要があるとき、私は元のコンテンツを変更しないが、元のデータのコピーが、変更はコピーを書かれています。あなたがその書き込み操作は、読み出し動作には影響を与えないことを保証できるように書いた後、その後、データの元のコピーを改訂し、交換。
CopyOnWriteArrayList
名前を見ることができますCopyOnWriteArrayList
満たすためにCopyOnWrite
呼び出され、ArrayListのをCopyOnWrite
:言って、あなたはメモリのブロックを変更したい場合は、コンピュータでは、我々は、メモリの元のブロックに含まれていない書かれているが、メモリコピーそれが終わった後、新たなインメモリの書き込み動作時に、それが元のメモリが出て回収することができ、新しいメモリに元のメモリポインタを指します。
六ConcurrentSkipListMapの
ConcurrentSkipListMapのを誘発するためには、どのようなシンプルなジャンプテーブルを理解するために皆をもたらすために。
単一リンクリストの場合、リストは、我々は唯一の最初から最後までリストを横切ることができるデータを検索する場合は、注文したので、当然低く、テーブルをジャンプすること効率が同じでない場合であっても。ジャンプテーブルは、バランスのとれたツリーに幾分類似すばやく見つけるために使用可能なデータ構造です。彼らはすぐに要素を見つけることができます。しかし、重要な違いがある:バランスの取れたツリーの挿入や削除のために、多くの場合、グローバル調整一度バランスの木につながる可能性があります。挿入ホップとテーブルを削除している間のみ、ローカルデータ構造の全体を操作する必要があります。このような利点は次のとおりです。高い同時実行の場合には、あなたは木のバランス全体でスレッドの安全性を保証するためにグローバルなロックが必要になります。ジャンプテーブルの場合は、あなただけの一部をロックする必要があります。このように、非常に同時環境の中で、あなたはより良い性能を持つことができます。そして、クエリのパフォーマンスの面で、時間複雑ジャンプテーブルは、JDKが地図を達成するためのジャンプテーブルを使用して、O(LOGN)ので、同時のデータ構造です。
自然のジャンプ台同時に複数のリストを維持しており、リストは階層化され、
ジャンプテーブルのすべての要素のリストを維持するための最低レベルは、それぞれの最上層は、以下のリストのサブセットの層です。
ジャンプテーブル内のリストのすべての要素がソートされます。とき検索は、あなたが上から順にリストを見つけることができます。求められている要素は、リストの現在の値よりも大きくなると、リストは、次のレベルに転送されます、探し続けます。これは、検索時に、検索は飛躍的であることを意味します。上記のように、要素18は、ジャンプテーブルを見つけます。
オリジナルの必要性が18を通過する際に18を検索し、今は7回を取ります。リストの長さについてインデックスは非常に明白であろう効率を向上しようとするときに大きなビルド。
容易に上から見た時のアルゴリズムのためのジャンプテーブルスペースを使用して、。
使用跳表实现Map 和使用哈希算法实现Map的另外一个不同之处是:哈希并不会保存元素的顺序,而跳表内所有的元素都是排序的。因此在对跳表进行遍历时,你会得到一个有序的结果。所以,如果你的应用需要有序性,那么跳表就是你不二的选择。JDK 中实现这一数据结构的类是ConcurrentSkipListMap。
1 AQS 简单介绍
AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。
AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。
2.1 AQS 原理概览
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
3 Semaphore(信号量)-允许多个线程同时访问
synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。 示例代码如下:
/** * * @author Snailclimb * @date 2018年9月30日 * @Description: 需要一次性拿一个许可的情况 */ public class SemaphoreExample1 { // 请求的数量 private static final int threadCount = 550; public static void main(String[] args) throws InterruptedException { // 创建一个具有固定线程数量的线程池对象(如果这里线程池的线程数量给太少的话你会发现执行的很慢) ExecutorService threadPool = Executors.newFixedThreadPool(300); // 一次只能允许执行的线程数量。 final Semaphore semaphore = new Semaphore(20); for (int i = 0; i < threadCount; i++) { final int threadnum = i; threadPool.execute(() -> {// Lambda 表达式的运用 try { semaphore.acquire();// 获取一个许可,所以可运行线程数量为20/1=20 test(threadnum); semaphore.release();// 释放一个许可 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }); } threadPool.shutdown(); System.out.println("finish"); } public static void test(int threadnum) throws InterruptedException { Thread.sleep(1000);// 模拟请求的耗时操作 System.out.println("threadnum:" + threadnum); Thread.sleep(1000);// 模拟请求的耗时操作 } }
除了 acquire
方法之外,另一个比较常用的与之对应的方法是tryAcquire
方法,该方法如果获取不到许可就立即返回false。
4 CountDownLatch (倒计时器)
CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。在Java并发中,countdownlatch的概念是一个常见的面试题,所以一定要确保你很好的理解了它。
4.1 CountDownLatch 的三种典型用法
①某一线程在开始运行前等待n个线程执行完毕。将 CountDownLatch 的计数器初始化为n :new CountDownLatch(n)
,每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown()
,当计数器的值变为0时,在CountDownLatch上 await()
的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
②实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 CountDownLatch
对象,将其计数器初始化为 1 :new CountDownLatch(1)
,多个线程在开始执行任务前首先 coundownlatch.await()
,当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。
③死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。
/** * * @author SnailClimb * @date 2018年10月1日 * @Description: CountDownLatch 使用方法示例 */ public class CountDownLatchExample1 { // 请求的数量 private static final int threadCount = 550; public static void main(String[] args) throws InterruptedException { // 创建一个具有固定线程数量的线程池对象(如果这里线程池的线程数量给太少的话你会发现执行的很慢) ExecutorService threadPool = Executors.newFixedThreadPool(300); final CountDownLatch countDownLatch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { final int threadnum = i; threadPool.execute(() -> {// Lambda 表达式的运用 try { test(threadnum); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { countDownLatch.countDown();// 表示一个请求已经被完成 } }); } countDownLatch.await(); threadPool.shutdown(); System.out.println("finish"); } public static void test(int threadnum) throws InterruptedException { Thread.sleep(1000);// 模拟请求的耗时操作 System.out.println("threadnum:" + threadnum); Thread.sleep(1000);// 模拟请求的耗时操作 } }
上面的代码中,我们定义了请求的数量为550,当这550个请求被处理完成之后,才会执行System.out.println("finish");
。
与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
其他N个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。
5 CyclicBarrier(循环栅栏)
CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties)
,其参数表示屏障拦截的线程数量,每个线程调用await
方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
5.1 CyclicBarrier 的应用场景
CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。
/** * * @author Snailclimb * @date 2018年10月1日 * @Description: 测试 CyclicBarrier 类中带参数的 await() 方法 */ public class CyclicBarrierExample2 { // 请求的数量 private static final int threadCount = 550; // 需要同步的线程数量 private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5); public static void main(String[] args) throws InterruptedException { // 创建线程池 ExecutorService threadPool = Executors.newFixedThreadPool(10); for (int i = 0; i < threadCount; i++) { final int threadNum = i; Thread.sleep(1000); threadPool.execute(() -> { try { test(threadNum); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } }); } threadPool.shutdown(); } public static void test(int threadnum) throws InterruptedException, BrokenBarrierException { System.out.println("threadnum:" + threadnum + "is ready"); try { /**等待60秒,保证子线程完全执行结束*/ cyclicBarrier.await(60, TimeUnit.SECONDS); } catch (Exception e) { System.out.println("-----CyclicBarrierException------"); } System.out.println("threadnum:" + threadnum + "is finish"); } }
threadnum:0is ready
threadnum:1is ready
threadnum:2is ready
threadnum:3is ready
threadnum:4is ready
threadnum:4is finish
threadnum:0is finish
threadnum:1is finish
threadnum:2is finish
threadnum:3is finish
threadnum:5is ready
threadnum:6is ready
threadnum:7is ready
threadnum:8is ready
threadnum:9is ready
threadnum:9is finish
threadnum:5is finish
threadnum:8is finish
threadnum:7is finish
threadnum:6is finish
......
可以看到当线程数量也就是请求数量达到我们定义的 5 个的时候, await
方法之后的方法才被执行。
另外,CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties, Runnable barrierAction)
,用于在线程到达屏障时,优先执行barrierAction
,方便处理更复杂的业务场景。示例代码如下:
/** * * @author SnailClimb * @date 2018年10月1日 * @Description: 新建 CyclicBarrier 的时候指定一个 Runnable */ public class CyclicBarrierExample3 { // 请求的数量 private static final int threadCount = 550; // 需要同步的线程数量 private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> { System.out.println("------当线程数达到之后,优先执行------"); }); public static void main(String[] args) throws InterruptedException { // 创建线程池 ExecutorService threadPool = Executors.newFixedThreadPool(10); for (int i = 0; i < threadCount; i++) { final int threadNum = i; Thread.sleep(1000); threadPool.execute(() -> { try { test(threadNum); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } }); } threadPool.shutdown(); } public static void test(int threadnum) throws InterruptedException, BrokenBarrierException { System.out.println("threadnum:" + threadnum + "is ready"); cyclicBarrier.await(); System.out.println("threadnum:" + threadnum + "is finish"); } }
threadnum:0is ready threadnum:1is ready threadnum:2is ready threadnum:3is ready threadnum:4is ready ------当线程数达到之后,优先执行------ threadnum:4is finish threadnum:0is finish threadnum:2is finish threadnum:1is finish threadnum:3is finish threadnum:5is ready threadnum:6is ready threadnum:7is ready threadnum:8is ready threadnum:9is ready ------当线程数达到之后,优先执行------ threadnum:9is finish threadnum:5is finish threadnum:6is finish threadnum:8is finish threadnum:7is finish ......
CountDownLatch: A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.(CountDownLatch: 一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;) CyclicBarrier : A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.(CyclicBarrier : 多个线程互相等待,直到到达同一个同步点,再继续一起执行。)