目录
jdk-api中文手册
常用的并发工具类
CountDownLatch:允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
可以理解为:统计xx-x的航班票数,必须等到多个航空公司(多线程)都统计完成后才能汇总展示给用户。
用法:CountDownLatch latch=new CountDownLatch(number),先初始化一个number;再多线程情况下
每调用一次countDown()方法,计数-1直到number为0时
,再配合await()使用;如果当前计数大于零,
则当前线程将被禁用以进行线程调度,并处于休眠状态。
常用方法:
-
public void countDown();
如果当前计数大于零,则它将递减。 如果新计数为零,则所有等待的线程都将被重新启用以进行线程调度。
-
getCount
public long getCount();
返回当前计数。
该方法通常用于调试和测试。
-
await()
public void await() throws InterruptedException;
public boolean await(long timeout,TimeUnit unit) throws InterruptedException;
导致当前线程等到锁存器计数到零,除非线程是interrupted 。
如果当前计数为零,则此方法立即返回。
如果当前计数大于零,则当前线程将被禁用以进行线程调度,并处于休眠状态,直至发生两件事情之一:
- 由于
countDown()
方法的调用,计数达到零; 要么一些其他线程interrupts当前线程。
示例:
public class CountDownLatchDemo {
private static CountDownLatch cdl = new CountDownLatch(3);
private static List<String> tasks = new ArrayList<String>();
public static void main(String[] args) throws InterruptedException {
final Thread[] threads = new Thread[3];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
int val = new Random().nextInt(5);
TimeUnit.SECONDS.sleep(val);
System.out.println(Thread.currentThread().getName() + " :完成任务【 " + val + "个】");
tasks.add(Thread.currentThread().getName() + " : " + val);
cdl.countDown();
}
});
threads[i].start();
threads[i].join();
}
//await()方法调用会校验cdl.getCount() ==0,为0则返回true,否则线程进入休眠.
cdl.await();
tasks.forEach(System.out::println);
}
}
CyclicBarrier:允许一组线程全部等待彼此达到共同屏障点的同步辅助。
可以理解为:比如马拉松比赛,必须等到所有参赛选手(多线程)都准备好,才能开始跑。
用法:CyclicBarrier barrier = new CyclicBarrier(10); 初始化一个屏障值number=10,初始值=0,每当线程调用一次await()则初始值+1、线程进入休眠状态;当初始值=10时,此刻屏障破损,所有线程再一起执行。
常用方法:
public int await() throws
等待所有parties已经在这个障碍上调用了await
。
如果当前线程不是最后一个线程,那么它被禁用以进行线程调度,并且处于休眠状态,直到发生下列事情之一:
-
最后一个线程到达; 要么一些其他线程当前线程为interrupts ;
-
要么一些其他线程interrupts其他等待线程之一;
-
要么一些其他线程在等待屏障时超时;
-
要么其他一些线程在这个屏障上调用
reset()
。
public int await(long timeout,
TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException
等待所有parties已经在此屏障上调用await
,或指定的等待时间过去。
如果当前线程不是最后一个线程,那么它被禁用以进行线程调度,并且处于休眠状态,直到发生下列事情之一:
- 最后一个线程到达; 要么
- 超过指定的超时 要么
- 一些其他线程当前线程interrupts ; 要么
- 其他一些线程interrupts其他等待线程; 要么
- 一些其他线程在等待屏障时超时; 要么
- 其他一些线程在这个障碍上调用
reset()
。
-
isBroken
public boolean isBroken()
查询这个障碍是否处于破碎状态。
结果
true
如果一个或多个参与方因施工或最后一次重置而导致中断或超时,或由于异常而导致屏障动作失败,则从此出现障碍;false
否则。
-
reset
public void reset()
将屏障重置为初始状态。 如果任何一方正在等待屏障,他们将返回
BrokenBarrierException
。 注意,由于其他原因,发生断线后的复位可能会复杂化; 线程需要以其他方式重新同步,并选择一个执行重置。 可能更好地为后续使用创建新的屏障。
-
getNumberWaiting
public int getNumberWaiting()
返回目前正在等待障碍的各方的数量。 此方法主要用于调试和断言。
结果
目前受阻于各方的数量
await()
示例:
public static void main(String[] args) {
//模拟马拉松跑步
CyclicBarrier barrier = new CyclicBarrier(10);
Thread[] player = new Thread[10];
for (int i = 0; i < player.length; i++) {
player[i] = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(10));
System.out.println(Thread.currentThread().getName() + " ok ");
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("player:" + Thread.currentThread().getName() + " running ");
}, "player[" + i + "]");
player[i].start();
}
}
Semaphore:一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()
都会阻塞,
直到许可证可用,然后才能使用它。 每个release()
添加许可证,潜在地释放阻塞获取方。 但是没有使用实际的
许可证对象; Semaphore
只保留可用数量的计数,并相应地执行。
可以理解:停车场业务,比如停车场只有10个车位,每辆车进入停车场则获取一个许可证,当许可证(信号量+1)=10时,不允许停车了(阻塞),当出去一辆车,则释放一个许可证(信号量-1),此刻通知还可以停一辆车,有效的控制停车流量。
使用方法:先初始化一个计数信号量count : Semaphore sp=new Semaphore(5);当线程获得调用acquire()获得许可凭证,则count+1,直到count = 5 /此刻如果再有线程调用则不能获取许可进入休眠;当获取许可的线程调用release()则释放资源,其他等待的线程继续获取许可。
常用方法:
-
Semaphore
public Semaphore(int permits)
创建一个
Semaphore
与给定数量的许可证和非公平公平设置。参数
permits
-permits
的初始许可证。 该值可能为负数,在这种情况下,必须在任何获取被授予之前发布释放。
如果当前线程:
然后InterruptedException
被关上,当前线程的中断状态被清除。
-
Semaphore
public void acquire()
throws InterruptedException从此信号量获取许可证,阻止直到可用,否则线程为interrupted 。
获得许可证,如果有可用并立即返回,则将可用许可证数量减少一个。
如果没有可用的许可证,那么当前线程将被禁用以进行线程调度,并且处于休眠状态,直至发生两件事情之一:
-
一些其他线程调用此信号量的
release()
方法,当前线程旁边将分配一个许可证; 要么 -
一些其他线程interrupts当前线程。
-
在进入该方法时设置了中断状态; 要么
-
是interrupted等候许可证,
常用并发容器
ConcurrentLinkedDeque:非阻塞式集合(Non-Blocking Collection),这类集合也包括添加和移除数据的方法。如果方法不能立即被执行,则返回null或抛出异常,但是调用这个方法的线程不会被阻塞。
Constructor and Description |
---|
ConcurrentLinkedDeque()
构造一个空的德克。 |
ConcurrentLinkedDeque(Collection<? extends E> c)
构造最初包含给定集合的元素的deque,以集合的迭代器的遍历顺序添加。 |
boolean |
add(E e)
在此deque的尾部插入指定的元素。 |
boolean |
addAll(Collection<? extends E> c)
按指定集合的迭代器返回的顺序将指定集合中的所有元素追加到该deque的末尾。 |
void |
addFirst(E e)
在此deque前面插入指定的元素。 |
void |
addLast(E e)
在此deque的末尾插入指定的元素。 |
void |
clear()
从这个deque中删除所有的元素。 |
boolean |
contains(Object o)
返回 |
E |
pop()
从这个deque表示的堆栈中弹出一个元素。 |
void |
push(E e)
将元素推送到由此deque代表的堆栈(换句话说,在该deque的头部),如果可以立即执行,而不违反容量限制,则抛出 |
E |
remove()
检索并删除由此deque表示的队列的头(换句话说,该deque的第一个元素)。 |
boolean |
remove(Object o)
删除第一个元素 |
示例:
public static void main(String[] args) throws InterruptedException {
ConcurrentLinkedDeque<String> cld = new ConcurrentLinkedDeque();
//添加数据
Thread[] add = new Thread[100];
for (int i = 0; i < 100; i++) {
add[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
cld.add(Thread.currentThread().getName() + ":Element " + j);
}
});
add[i].start();
add[i].join();
}
System.out.println("after add size:" + cld.size());
//移除数据
Thread[] poll = new Thread[100];
for (int i = 0; i < 100; i++) {
poll[i] = new Thread(() -> {
for (int j = 0; j < 500; j++) {
cld.pollLast();
cld.pollFirst();
}
});
poll[i].start();
poll[i].join();
}
System.out.println("after poll size:" + cld.size());
}
LinkedBlockingDeque:阻塞式集合(Blocking Collection),这类集合包括添加和移除数据的方法。当集合已满或为空时,被调用的添加或者移除方法就不能立即被执行,那么调用这个方法的线程将被阻塞,一直到该方法可以被成功执行。
LinkedBlockingDeque()
创建一个 |
LinkedBlockingDeque(Collection<? extends E> c)
创建一个 |
LinkedBlockingDeque(int capacity)
创建一个具有给定(固定)容量的 |
public static void main(String[] args) throws InterruptedException {
LinkedBlockingDeque<String> list = new LinkedBlockingDeque();
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 5; j++) {
String str = new String(i + ":" + j);
try {
list.put(str.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("put: " + str + (new Date()));
}
}
});
thread.start();
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 5; j++) {
String str = list.take();
System.out.println("take: " + str + " size:" + list.size());
TimeUnit.SECONDS.sleep(2);
}
}
}
并发容器有哪些分类
1.ConcurrentHashMap
对应的非并发容器:HashMap
目标:代替Hashtable、synchronizedMap,支持复合操作
原理:JDK6中采用一种更加细粒度的加锁机制Segment“分段锁”,JDK8中采用CAS无锁算法。
2.CopyOnWriteArrayList
对应的非并发容器:ArrayList
目标:代替Vector、synchronizedList
原理:利用高并发往往是读多写少的特性,对读操作不加锁,对写操作,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用,并通过volatile 保证其可见性,当然写操作的锁是必不可少的了。
3.CopyOnWriteArraySet
对应的非并发容器:HashSet
目标:代替synchronizedSet
原理:基于CopyOnWriteArrayList实现,其唯一的不同是在add时调用的是CopyOnWriteArrayList的addIfAbsent方法,其遍历当前Object数组,如Object数组中已有了当前元素,则直接返回,如果没有则放入Object数组的尾部,并返回。
4.ConcurrentSkipListMap
对应的非并发容器:TreeMap
目标:代替synchronizedSortedMap(TreeMap)
原理:Skip list(跳表)是一种可以代替平衡树的数据结构,默认是按照Key值升序的。
5.ConcurrentSkipListSet
对应的非并发容器:TreeSet
目标:代替synchronizedSortedSet
原理:内部基于ConcurrentSkipListMap实现
6.ConcurrentLinkedQueue
不会阻塞的队列
对应的非并发容器:Queue
原理:基于链表实现的FIFO队列(LinkedList的并发版本)
7.LinkedBlockingQueue、ArrayBlockingQueue、PriorityBlockingQueue
对应的非并发容器:BlockingQueue
特点:拓展了Queue,增加了可阻塞的插入和获取等操作
原理:通过ReentrantLock实现线程安全,通过Condition实现阻塞和唤醒
实现类:
- LinkedBlockingQueue:基于链表实现的可阻塞的FIFO队列
- ArrayBlockingQueue:基于数组实现的可阻塞的FIFO队列
- PriorityBlockingQueue:按优先级排序的队列