并发集合
普通的集合中有List(ArrayList|LinkedList)、Set(HashSet|TreeSet)和Map(HashMap|TreeMap)集合,这些集合只适合在单线程情况下使用。
在Collecionts工具类中有synchronized开头方法可以把单线程集合转成支持并发的集合,但是效率不高,很少使用。
为了更好的实现集合的高并发访问处理,JUC创建了一组新的集合工具类,其使用和一般的集合一样,使用方法参照 “Java之集合” 的分类。
1. List和Set集合
- CopyOnWriteArrayList相当于线程安全的ArrayList,实现了List接口,支持高并发
- CopyOnWriteArraySet相当于线程安全的HashSet,它继承了AbstractSet类,内部是通过CopyOnWriteArrayList实现的,所以是有序的 set 集合
2.Map集合
- ConcurrentHashMap是线程安全的哈希表(相当于线程安全的HashMap);它继承于AbstractMap类,并且实现ConcurrentMap接口。
该集合在jdk1.7之前是采用的分段锁机制,将哈希表分为16段,每段是一个锁,每个段又都是哈希表,写入扩容锁定,读取共享,类似于读写锁机制。
在jdk1.8之后,取消分段锁,改用CAS无锁算法,提高写入小路,另外put方法有锁,但是get方法没锁,使读取速度与普通HashMap一致。
如果哈希表位置已经有链表,则默认将链表的表头采用synchronized锁住,没有则直接通过算法添加元素。 - ConcurrentSkipListMap是线程安全的有序的哈希表(相当于线程安全的TreeMap);它继承于AbstactMap类,并且实现ConcurrentNavigableMap接口。它是通过“跳表”来实现的,它支持并发;
- ConcurrentSkipListSet是线程安全的有序的集合(相当于线程安全的TreeSet);它继承于AbstractSet,并实现了NavigableSet接口,是通过ConcurrentSkipListMap实现的,它也支持并发。
3.Queue队列
- ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列
- LinkedBlockingQueue是单向链表实现的(指定大小)阻塞队列,该队列按FIFO(先进先出)排序元素
- LinkedBlockingDeque是双向链表实现的(指定大小)双向并发阻塞队列,该阻塞队列同时支持FIFO和FILO两种操作方式;
- ConcurrentLinkedQueue是单向链表实现的无界队列,该队列按FIFO(先进先出)排序元素。
- ConcurrentLinkedDeque是双向链表实现的无界队列,该队列同时支持FIFO和FILO两种操作方式。
4.使用并发实现生产者和消费者模式
并发集合本身具有高并发特性,且ArrayBlockingQueue队列满足先进先出的规则,不需要wait和notify机制,使用很方便,但是要保证双方必须使用同一个集合。
//生产者类
public class Producer extends Thread{
private ArrayBlockingQueue<String> abq;
public Producer(ArrayBlockingQueue<String> abq) {
this.abq = abq;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
abq.put(Thread.currentThread().getName()+"生产"+(i+1)+"号产品");
System.out.println(Thread.currentThread().getName()+"生产"+(i+1)+"号产品");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者类
public class Consumer extends Thread{
private ArrayBlockingQueue<String> abq;
public Consumer(ArrayBlockingQueue<String> abq) {
this.abq = abq;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
String take = abq.take();
System.out.println(Thread.currentThread().getName()+"消费了"+take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//main方法
public class Test {
public static void main(String[] args) {
//生产者生产满5停止,即队列满了就停止,然后消费者消费,队列特点就是先进先出,取出并删除元素
ArrayBlockingQueue<String> arr = new ArrayBlockingQueue<>(5);
new Producer(arr).start();
new Consumer(arr).start();
}
}
同步工具类
1.CountDownLatch类
CountDownLatch(闭锁)是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
闭锁可以延迟线程的进度直到其到达终止状态,可以用来确保某些活动直到其他活动都完成才能继续执行:
(1)确保某个计算在其需要的所有资源都被初始化后才能继续执行。
(2)确保某个服务在其依赖的所有其他服务都已经启动之后才启动。
(3)等待直到某个操作所有参与者都执行完毕其他线程才能继续执行。
有点类似于join()方法,只不过是让更多的线程统一阻塞某一线程,当这些线程一个个执行完毕,最后再执行被阻塞线程,而不是join某一个线程。
相关方法
//构造方法,构造一个用给定计数初始化的 CountDownLatch
//对10个线程进行处理,当10个线程全部执行完毕,再执行被阻塞线程
CountDownLatch countDownLatch = new CountDownLatch(10);
//使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断
//该语句写在哪里就是阻塞哪个线程
void await();
//递减锁存器的计数,如果计数到达零,则释放所有等待的线程
void countDown();
//返回当前计数
long getCount();
计算整个程序执行的时间
main线程和其他子线程都是同级关系,所以,会出现main线程执行完毕,计算的时间结果仅仅是main线程自己的,而非整个程序的,所以,需要进行CountDownLatch。
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
long start = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
new MyThread(countDownLatch).start();
}
countDownLatch.await(); //相当于将10个子线程都join,等待执行的子线程数量到0,join结束,主线程开始,这里放到了main方法,就是将main阻塞
long end = System.currentTimeMillis(); //只有让子线程join,才能让主线程最后结束,才能计算毫秒值
System.out.println(end - start);
}
//静态内部类
static class MyThread extends Thread {
private CountDownLatch countDownLatch;
public MyThread(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行了。。。");
int c = 0;
for (int i = -999999; i < 999999999; i++) {
for (int j = -999999; j < 99999999; j++) {
c = i + j;
}
}
System.out.println(Thread.currentThread().getName() + "结束了。。。");
countDownLatch.countDown(); //递减
}
}
}
注意
1. 如果CountDownLatch初始的线程数量小于实际创建的线程数量,则计时到0的时候,主线程阻塞结束,开始执行,剩余的子线程随后继续执行。
2. 如果大于实际的线程数量,则程序会无法退出,主线程一直被阻塞。
3.循环写在线程创建的外面,则main线程直接连续创建多个线程,然后这些线程各自抢到CPU执行;循环写到线程run()方法里面,则每个线程抢到CPU后自己执行多次后才让出时间片。
2.CyclicBarrier类
CyclicBarrier类似(屏障),表面意思理解为可循环使用的屏障,作用是让一组线程在到达一个屏障时被阻塞,等到最后一个线程到达屏障点,才会让所有被拦截的线程一起继续运行。
相关方法
//创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动
CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
//创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作
CyclicBarrier(int parties, Runnable barrierAction);
//在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待
int await();
运动员起跑
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new Runnable() {
@Override
public void run() {
for (int i = 10; i > -1; i--) {
try {
Thread.sleep(1000);
System.out.println(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("比赛开始!!");
}
});
for (int i = 0; i < 10; i++) {
Thread.sleep(2000);
new SportMan(i + "号", cyclicBarrier).start();
}
}
//静态内部类
static class SportMan extends Thread {
CyclicBarrier cyclicBarrier;
String name;
public SportMan(String name, CyclicBarrier cyclicBarrier) {
this.name = name;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
System.out.println(this.name + "运动员就绪");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
注意: 如果CyclicBarrier的初始数量少于实际创建的线程数量,则会循环执行,直到所有线程都运行完毕;反之,程序不退出,死锁。
3.Semaphore信号量
Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。
当正在并发的线程数量低于阈值,则增加线程,高于阈值,则以阈值数量为准,多出的线程进行等待,总之,就是保证正在并发的线程数量始终是阈值。
相关方法
//创建具有给定的许可数和非公平的公平设置的 Semaphore
Semaphore(int permits);
//创建具有给定的许可数和给定的公平设置的 Semaphore
Semaphore(int permits, boolean fair);
//从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
void acquire();
//释放一个许可,将其返回给信号量,用在finally中
void release()
案例演示
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(2);
for (int i = 0; i < 20; i++) {
Thread.sleep(2000);
new MyThread(semaphore).start();
}
}
//内部类
static class MyThread extends Thread {
private Semaphore semaphore;
public MyThread(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
semaphore.acquire();//获取锁
System.out.println(Thread.currentThread().getName() + "开始执行" + simpleDateFormat.format(new Date()));
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "结束了" + simpleDateFormat.format(new Date()));
semaphore.release();
}
}
}
}
//打印结果,每次都保证有两个执行:
Thread-0开始执行2019-08-17 19:01:06
Thread-1开始执行2019-08-17 19:01:08
Thread-0结束了2019-08-17 19:01:08
Thread-1结束了2019-08-17 19:01:10
Thread-2开始执行2019-08-17 19:01:10
Thread-2结束了2019-08-17 19:01:12
Thread-3开始执行2019-08-17 19:01:12
Thread-3结束了2019-08-17 19:01:14
Thread-4开始执行2019-08-17 19:01:14
Thread-5开始执行2019-08-17 19:01:16
Thread-4结束了2019-08-17 19:01:16
来源:https://blog.csdn.net/qq_45337431/article/details/99686936