1、JUC简介

在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,

用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中的 Collection 实现等,大大的提高了java的并发性能。

2、JUC之AQS

AQS(AbstractQueuedSynchronizer)是JUC的核心,其特点:

(1)使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基本框架;

(2)利用了一个int型表示状态;

(3)使用方法是继承

(4)子类通过继承并通过实现它的方法管理其状态(acquire和release)的方法操纵状态

(5)可以同时实现排它锁和共享锁模式(独占、共享)

下面是AQS数据结构的底层实现:

java 多线程 map 线程安全_java 多线程 map 线程安全


AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。

AQS(AbstractQueuedSynchronizer)原理图:

java 多线程 map 线程安全_java_02


3、CountDouwnLatch

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。

CountDownLatch 的两种典型用法:

  • CountDouwnLatch相当于一个计数器,是一个原子操作,调用了该类的await()方法的线程会一直处于阻塞的状态,直到其他线程调用countDown()方法使得计数器的值等于0,调用了该类的await()方法线程机会重新执行。但是该过程只能执行一次,因为计数器的值不能重(CyclicBarrier类可以重置)
  • 实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的
    CountDownLatch 对象,将其计数器初始化为 1 :new CountDownLatch(1),多个线程在开始执行任务前首先
    coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。

4、Semaphore
Semaphore(信号量)可以指定多个线程同时访问某个资源,维护资源的访问个数。Semaphore通过acquire()获取许可,release()方法释放许可,Semaphore经常用于限制获取某种资源的线程数量。

Semaphore 有两种模式:

  • 公平模式: 调用acquire的顺序就是获取许可证的顺序,遵循FIFO;
  • 非公平模式: 抢占式的。

5、CyclicBarrier
CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。

java 多线程 map 线程安全_应用场景_03


CyclicBarrier 的应用场景:

CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。

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 {
			cyclicBarrier.await(2000, TimeUnit.MILLISECONDS);
		} catch (Exception e) {
			System.out.println("-----CyclicBarrierException------");
		}
		System.out.println("threadnum:" + threadnum + "is finish");
	}

}

注意:
CyclicBarrier和CountDownLatch的区别:

CountDownLatch是计数器,只能使用一次,而CyclicBarrier的计数器提供reset功能,可以多次使用。

对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。

CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。

java 多线程 map 线程安全_System_04


6 ReentrantLock

java里面的锁主要分为两类,一是Synchronized修饰的锁,二是JUC提供的锁。

这里说说ReentrantLock与Synchronized区别:

(1)可重入性

(2)锁的实现方式

(3)性能的区别

(4)功能的区别

ReentrantLock独有的功能:

(1) 可指定是公平锁还是非公平锁

(2) 提供了一个Condition类,可以分组唤醒需要唤醒的线程

(3) 提供能够中断等待锁的线程的机制