多线程并发总结

一、Synchronized

1.1、原子性

  • 原子性:
    数据库的事务:ACID
    A:原子性-事务是一个最小的执行的单位,一次事务的多次操作要么都成功,要么都失败。
    并发编程的原子性:一个或多个指令在CPU执行过程中不允许中断的。
    i++;操作是原子性?
    肯定不是:i++操作一共有三个指令
    在这里插入图片描述

getfield:从主内存拉取数据到CPU寄存器

iadd:在寄存器内部对数据进行+1

putfield:将CPU寄存器中的结果甩到主内存中

如何保证i++是原子性的。

锁:synchronized,lock,Atomic(CAS)
在这里插入图片描述
使用lock锁也会有类似的概念,也就是在操作i++的三个指令前,先基于AQS成功修改state后才可以操作

使用synchronized和lock锁时,可能会触发将线程挂起的操作,而这种操作会触发内核态和用户态的切换,从而导致消耗资源。

CAS方式就相对synchronized和lock锁的效率更高(也说不定),因为CAS不会触发线程挂起操作!

CAS:compare and swap

线程基于CAS修改数据的方式:先获取主内存数据,在修改之前,先比较数据是否一致,如果一致修改主内存数据,如果不一致,放弃这次修改

CAS就是比较和交换,而比较和交换是一个原子操作
在这里插入图片描述
CAS在Java层面就是Unsafe类中提供的一个native方法,这个方法只提供了CAS成功返回true,失败返回false,如果需要重试策略,自己实现!

CAS问题:

CAS只能对一个变量的修改实现原子性。
CAS存在ABA问题。
A线程修改主内存数据从1~2,卡在了获取1之后。
B线程修改主内存数据从1~2,完成。
C线程修改主内存数据从2~1,完成。
A线程执行CAS操作,发现主内存是1,没问题,直接修改
解决方案:加版本号
在CAS执行次数过多,但是依旧无法实现对数据的修改,CPU会一直调度这个线程,造成对CPU的性能损耗
synchronized的实现方式:CAS自旋一定次数后,如果还不成,挂起线程
LongAdder的实现方式:当CAS失败后,将操作的值,存储起来,后续一起添加
CAS:在多核情况下,有lock指令保证只有一个线程在执行当前CAS

1.2 有序性

指令在CPU调度执行时,CPU会为了提升执行效率,在不影响结果的前提下,对CPU指令进行重新排序。

如果不希望CPU对指定进行重排序,怎么办?

可以对属性追加volatile修饰,就不会对当前属性的操作进行指令重排序。

什么时候指令重排:满足happens-before原则,即可重排序

单例模式-DCL双重判断。

申请内存,初始化,关联是正常顺序,如果CPU对指令重排,可能会造成

申请内存,关联,初始化,在还没有初始化时,其他线程来获取数据,导致获取到的数据虽然有地址引用,但是内部的数据还没初始化,都是默认值,导致使用时,可能出现与预期不符的结果

1.3 可见性

可见性:前面说过CPU在处理时,需要将主内存数据甩到我的寄存机中再执行指令,指向完指令后,需要将寄存器数据扔回到主内存中。倒是寄存器数据同步到主内存是遵循MESI协议的,说人话就是,

不是每次操作结束就将CPU缓存数据同步到主内存。造成多个线程看到的数据不一样。

volatile每次操作后,立即同步数据到主内存。

synchronized,触发同步数据到主内存。

final,也可以解决可见性问题。

2、synchronized使用

synchronized方法

synchronized代码块

类锁和对象锁:

类锁:基础当前类的Class加锁

对象锁:基于this对象加锁

synchronized是互斥锁,每个线程获取synchronized时,基于synchronized绑定的对象去获取锁!

synchronized锁是基于对象实现的!

synchronized是如何基于对象实现的互斥锁,先了解对象再内存中是如何存储的。
在这里插入图片描述
在这里插入图片描述

3、synchronized锁升级

synchronized不存在从重量级锁降到偏向或者轻量
synchronized 在偏向锁升级到轻量级锁时,会涉及到偏向锁撤销,需要等到一个安全点,才可以撤销,并发偏向锁撤销比较消耗资源。
在程序启动时,偏向锁有一个延迟开启的操作,因为项目启动时,ClassLoader会加载.class文件,这里会设计到synchronized操作

  • 无锁状态、匿名偏向状态:没有线程拿锁。
  • 偏向锁:没有线程的竞争,只有一个线程再获取锁资源。线程竞争锁资源时,发现当前synchronized没有线程占用资源,并且锁都是偏向锁,使用CAS的方式,设置o的线程ID为当前线程,获取到锁资源,下次当前线程再次获取时,只需要判断是偏向锁,并且线程ID是当前线程ID即可,直接获取到锁资源。
  • 轻量级锁:偏向锁出现竞争时,会升级到轻量级锁。
  • 重量级锁:轻量级锁CAS一段次数后,没有拿到锁资源,升级为重量级锁
  • 偏向锁是延迟开启的,并且在开启偏向锁之后,默认不存在无锁状态,只存在匿名偏向Synchronized因为不存在从重量级锁降级到偏向或者轻量。

锁消除:线程在执行一段synchronized 代码块时,发现没有共享数据的操作,自动把synchronized去掉
锁膨胀:在一个多次循环的操作中频繁的获取和释放锁资源,可能会有花

二、ReentrantLock源码

1、ReentrantLock介绍

如果竞争比较激烈,推荐lock锁,效率更高。
如果没有竞争,推荐synchronized
原因:synchronized只有锁升级,当升级到重量级锁后,无法降级到轻量级,偏向锁。
synchronized是非公平锁,lock是公平+非公平
lock锁更完善,lock可以使用trylock指定等待锁的时间。
lock锁还提供了lockintereuptibly允许线程在获取锁的期间被中断。
synchronized基于对象实现,lock锁基于AQS+CAS实现。

2、ReentranctLock的lock方法源码

非公平锁:上来就先尝试将state从0修改为1,如果成功,代表获取锁资源,如果没有成功,调用acquire
公平锁:调用acquire
state是AQS中的一个由volatile修饰的int类型变量,多个线程会通过CAS的方式修改state,在并发情况下,只会有一个线程成功的修改state(从0~1)
如果修改state失败怎么办?
如果线程没有拿到锁资源,会到AQS的双向链表中排队等待(在其间,线程节能会挂起)
AQS的双向链表是基于内部类Node维护的,
//公平锁
	final void lock() {
            acquire(1);
        }
//非公平锁
    final void lock() {
            if (compareAndSetState(0, 1))
                	setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

3、ReentranctLock的acquire方法源码

acquire是一个业务方法,里面并没有实际的业务处理
调用tryAcquire方法:尝试获取锁资源(非公平、公平),拿到锁资源,返回true,直接结束方法,没有拿到所资源,需要执行&&后面的方法。
当没有获取锁资源后,会先吊用addwaiter方法:会将没有获取到锁资源的线程封装为Node对象,//并且插入到AQS的队列的末尾,并且作为tail
继续调用Acquirequeue方法,查看当前排队的Node是否在队列的前面,如果在前面,尝试获取锁资源,如果没在前面,就将线程挂起。
// 核心acquire arg = 1 
public final void acquire(int arg) { 
//1. 调用tryAcquire方法:尝试获取锁资源(非公平、公平),拿到锁资源,返回true,直接结束方法。 没有拿到锁资源, // 需要执行&&后面的方法
 //2. 当没有获取锁资源后,会先调用addWaiter:会将没有获取到锁资源的线程封装为Node对象, // 并且插入到AQS的队列的末尾,并且作为tail 
 //3. 继续调用acquireQueued方法,查看当前排队的Node是否在队列的前面,如果在前面(head的next),尝试获取锁资源 // 如果没在前面,尝试将线程挂起,阻塞起来!
  if (!tryAcquire(arg) && 
  		acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
   		selfInterrupt(); 
   }

4、ReentranctLock的tryAcquire方法源码

tryAcquire分为公平和非公平两种、
tryAcquire主要做了两件事:
	1、如果state为0,尝试获取锁资源。
	2、如果state不为0,看一下是不是锁重入操作。
// 非公平锁实现! 
final boolean nonfairTryAcquire(int acquires) { 
// 拿到当前线程! 
	final Thread current = Thread.currentThread(); 
	// 拿到AQS的state 
	int c = getState(); 
	// 如果state == 0,说明没有线程占用着当前的锁资源 
	if (c == 0) { 
	// 没人占用锁资源,我直接抢一波(不管有没有线程在排队) 
	if (compareAndSetState(0, acquires)) {
		 // 将当前占用这个互斥锁的线程属性设置为当前线程 setExclusiveOwnerThread(current); // 返回true,拿锁成功 
		 return true; } 
		 } 
		 // 当前state != 0,说明有线程占用着锁资源 
		 // 判断拿着锁的线程是不是当前线程(锁重入) 
	else if (current == getExclusiveOwnerThread()) {
	 // 将state再次+1 
	 int nextc = c + acquires; 
	 // 锁重入是否超过最大限制 // 01111111 11111111 11111111 11111111 + 1
	  // 10000000 00000000 00000000 00000000 // 抛出error 
	  if (nextc < 0) throw new Error("Maximum lock count exceeded"); 
	  // 将值设置给state setState(nextc); 
	  // 返回true,拿锁成功 
	  return true; } 
	 return false; }

公平锁实现

// 公平锁实现 
	protected final boolean tryAcquire(int acquires) { 
	// 拿到当前线程! 
	final Thread current = Thread.currentThread(); 
	// 拿到AQS的state int c = getState();
	 // 阿巴阿巴~~~~ 
	 	if (c == 0) {
	  // 判断是否有线程在排队,如果有线程排队,返回true,配上前面的!,那会直接不执行返回最外层的false
	   if (!hasQueuedPredecessors() && 
	   // 如果没有线程排队,直接CAS尝试获取锁资源
	    compareAndSetState(0, acquires)) { 
	    	setExclusiveOwnerThread(current); 
	    	return true; }
	    	 } 
	   else if (current == getExclusiveOwnerThread()) { 
	    	 int nextc = c + acquires;
	    	 if (nextc < 0) 
	    	 	 throw new Error("Maximum lock countexceeded"); 
	    	 	 setState(nextc); 
	    	 return true; } 	 
	    return false; }

5、ReentranctLock的addWaiter方法源码

在获取锁资源失败后,需要将当前线程封装为Node对象,并且插入到AQS队列的末尾

// 将当前线程封装为Node对象,并且插入到AQS队列的末尾
 private Node addWaiter(Node mode) {
  // 将当前线程封装为Node对象,mode为null,代表互斥锁 
  	Node node = new Node(Thread.currentThread(), mode); 
  // pred是tail节点
   	Node pred = tail; 
   	// 如果pred不为null,有线程正在排队 
   	if (pred != null) { 
   	// 将当前节点的prev,指定tail尾节点 
   	node.prev = pred; 
   	// 以CAS的方式,将当前节点变为tail节点 
   	if (compareAndSetTail(pred, node)) { 
   	// 之前的tail的next指向当前节点 
   	pred.next = node;
   	 return node; 
   	} 
   	} 
   	// 添加的流程为, 自己prev指向、tail指向自己、前节点next指向我 
   	// 如果上述方式,CAS操作失败,导致加入到AQS末尾失败,如果失败,就基于enq的方式添加到AQS队列 
   	enq(node); 
   	return node; 
   }// enq,无论怎样都添加进入
    private Node enq(final Node node) { 
    for (;;) { 
    // 拿到tail
    	 Node t = tail; 
     // 如果tail为null,说明当前没有Node在队列中 
     	if (t == null) { 
     	// 创建一个新的Node作为head,并且将tail和head指向一个Node 
     	if (compareAndSetHead(new Node()))
     	 tail = head; 
     	 } else { 
     	 // 和上述代码一致! 
     	 node.prev = t;
     	  if (compareAndSetTail(t, node)) { 
     	  t.next = node; 
     	  return t; } } } }

6、ReentranctLock的Acquirequeue方法源码

acquireQueued方法会查看当前排队的Node是否是head的next,如果是,尝试获取锁资源,如果不是或者获取锁资源失败那么就尝试将当前Node的线程挂起(unsafe.park())

在挂起线程前,需要确认当前节点的上一个节点的状态必须是小于等于0,

如果为1,代表是取消的节点,不能挂起

如果为-1,代表挂起当前线程

如果为-2,-3,需要将状态改为-1之后,才能挂起当前线程

// acquireQueued方法
// 查看当前排队的Node是否是head的next,
// 如果是,尝试获取锁资源,
// 如果不是或者获取锁资源失败那么就尝试将当前Node的线程挂起(unsafe.park())
final boolean acquireQueued(final Node node, int arg) {
    // 标识。
    boolean failed = true;
    try {
        // 循环走起
        for (;;) {
            // 拿到上一个节点
            final Node p = node.predecessor();
            if (p == head && // 说明当前节点是head的next
                tryAcquire(arg)) { // 竞争锁资源,成功:true,失败:false
                // 进来说明拿到锁资源成功
                // 将当前节点置位head,thread和prev属性置位null
                setHead(node);
                // 帮助快速GC
                p.next = null; 
                // 设置获取锁资源成功
                failed = false;
                // 不管线程中断。
                return interrupted;
            }
            // 如果不是或者获取锁资源失败,尝试将线程挂起
            // 第一个事情,当前节点的上一个节点的状态正常!
            // 第二个事情,挂起线程
            if (shouldParkAfterFailedAcquire(p, node) &&
				// 通过LockSupport将当前线程挂起
                parkAndCheckInterrupt())
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

// 确保上一个节点状态是正确的
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 拿到上一个节点的状态
    int ws = pred.waitStatus;
    // 如果上一个节点为 -1
    if (ws == Node.SIGNAL)
        // 返回true,挂起线程
        return true;
    // 如果上一个节点是取消状态
    if (ws > 0) {
        // 循环往前找,找到一个状态小于等于0的节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 将小于等于0的节点状态该为-1
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

7、ReentranctLock的unlock方法源码

释放锁资源:
	1、state-1;
	2、如果state减为0,唤醒在队列中排队的Node(一定唤醒离Node最近的)
// 真正释放锁资源的方法 
public final boolean release(int arg) { 
		// 核心的释放锁资源方法 
		if (tryRelease(arg)) { 
		// 释放锁资源释放干净了。 (state == 0)
		 Node h = head;
		  // 如果头节点不为null,并且头节点的状态不为0,唤醒排队的线程 
		 if (h != null && h.waitStatus != 0)
		  // 唤醒线程 
		  unparkSuccessor(h);
		   return true;
		 }
		  // 释放锁成功,但是state != 0 
	return false; 
	} 
	// 核心的释放锁资源方法 
	protected final boolean tryRelease(int releases) { 
	// 获取state - 1 
	int c = getState() - releases; 
	// 如果释放锁的线程不是占用锁的线程,抛异常 
	if (Thread.currentThread() != getExclusiveOwnerThread())
	 throw new IllegalMonitorStateException(); 
	 // 是否成功的将锁资源释放利索 (state == 0)
	  boolean free = false; 
	  if (c == 0) { 
	  // 锁资源释放干净。 
	  		free = true;
	  		 // 将占用锁资源的属性设置为null 		
	  		 	setExclusiveOwnerThread(null); 
	  		 	} 
	  		 // 将state赋值 
	  		 setState(c);
	  	 // 返回true,代表释放干净了 
	  return free; } 
	 // 唤醒节点 
	 private void unparkSuccessor(Node node) {
	  // 拿到头节点状态 
	  int ws = node.waitStatus;
	   // 如果头节点状态小于0,换为0 
	   if (ws < 0) compareAndSetWaitStatus(node, ws, 0);
	    // 拿到当前节点的next
	     Node s = node.next; 
	     // 如果s == null ,或者s的状态为1 
	     if (s == null || s.waitStatus > 0) { 
	     // next节点不需要唤醒,需要唤醒next的next 
	     s = null; 
	     // 从尾部往前找,找到状态正常的节点。(小于等于0代表正常状态) 
	     for (Node t = tail; t != null && t != node; t =t.prev) 
	     		if (t.waitStatus <= 0) s = t; } 
	     // 经过循环的获取,如果拿到状态正常的节点,并且不为null
	      if (s != null) 
	      // 唤醒线程 
	      LockSupport.unpark(s.thread);
	       }

为什么唤醒线程时,从尾部往前找,而不是从头部往后找???
因为在addWaiter操作时,是先将当前Node的prev指针指向前面的节点,然后是将tail赋值给当前的Node,最后才是上一个节点的next指针,指向当前Node。

三、ReentrantReadWriteLock读写锁源码

1、为什末要出现读写锁

因为ReentrantLock是互斥锁,如果一个操作时读多写少,同时还需要保证线程安全,那么使用ReentrantLock会导致效率比较低。
因为多个线程在对同一个数据进行读操作时,也不会造成线程安全问题。
所以出现了ReentranctReadWriteLock锁:
	读操作时共享的。
	写操作时互斥的。
	读写操作是互斥的。
	写读操作是互斥的。
	单个线程获取写锁后,再次获取读锁,可以拿到。(写读可重入)
	单个线程获取读锁后,再次获取写锁,拿不到。

使用方式:

public class XxxTest {
    // 读写锁!
    static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    // 写锁
    static ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();

    // 读锁
    static ReentrantReadWriteLock.ReadLock readLock = lock.readLock();

    public static void main(String[] args) throws InterruptedException {
        readLock.lock();
        try {
            System.out.println("拿到读锁!");
        } finally {
            readLock.unlock();
        }

        writeLock.lock();
        try {
            System.out.println("拿到写锁!");
        } finally {
            writeLock.unlock();
        }
    }
}

2、读写锁的核心思想

ReentrantReadWriteLock还是基于AQS实现的,很多功能的实现和ReentrantLock类似 还是基于AQS的state来确定当前线程是否拿到锁资源。
state表示读锁:将state的高16位作为读锁的标识
state表示写锁:将state的低16位作为写锁的标识
锁重入问题:
写锁重入怎么玩:因为写操作和其他操作是互斥的,代表同一时间,只有一个线程持有写锁,只要锁重入,就对低位+1即可,而且锁重入的限制,从原来的2^31-1,变为了

每个读操作的线程,在获取读锁时,都需要开辟一个ThreadLocal,读写锁为了优化这个事情,做了两手操作:

  • 第一个拿到读锁的线程,不需要ThreadLocal记录重入锁,在读写锁内有一个firstRead记录冲入次数
  • 还记录了最后一个拿到读锁的线程的冲入次数,交给cacheHoldCounter属性标识,可以避免频繁的在锁重入时,从TL中获取。

读锁重入的时候就不操作state了?不对,每次锁重入还要修改state,只是记录当前线程锁重入的次数,需要基于ThreadLocal记录
00000000 00000000 00000000 00000000 : state

写锁:

00000000 00000000 00000000 00000001

写锁:

00000000 00000000 00000000 00000010

A读锁:拿不到,排队

00000000 00000000 00000000 00000010

写锁全部释放(唤醒)

00000000 00000000 00000000 00000000

A读锁:

00000000 00000001 00000000 00000000

B读锁:

00000000 00000010 00000000 00000000

B再次读锁:

00000000 00000011 00000000 00000000

每个读操作的线程,在获取读锁时,都需要开辟一个ThreadLocal。读写锁为了优化这个事情,做了两手操作:

  • 第一个拿到读锁的线程,不用ThreadLocal记录重入次数,在读写锁内有有一个firstRead记录重入次数
  • 还记录了最后一个拿到读锁的线程的重入次数,交给cachedHoldCounter属性标识,可以避免频繁的在锁重入时,从TL中获取

3、写锁的操作

3.1 、写锁加锁-acquire

public final void acquire(int arg) {
 // 尝试获取锁资源(看一下,能否以CAS的方式将state 从0 ~ 1,改成功,拿锁成功)
  // 成功走人 
  // 不成功执行下面方法 
  if (!tryAcquire(arg) && 
  // addWaiter:将当前没按到锁资源的,封装成Node,排到AQS里 
  // acquireQueued:当前排队的能否竞争锁资源,不能挂起线程阻塞 
  acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }

因为都是AQS的实现,主要看tryAcquire

// state,高16:读,低16:写
00000000 00000000 00000000 00000000

00000000 00000001 00000000 00000000 - SHARED_UNIT

00000000 00000000 11111111 11111111 - MAX_COUNT

00000000 00000000 11111111 11111111 - EXCLUSIVE_MASK
&
00000000 00000000 00000000 00000001 

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

// 只拿到表示读锁的高16位。
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
// 只拿到表示写锁的低16位。
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }


// 读写锁的写锁,获取流程
protected final boolean tryAcquire(int acquires) {
    // 拿到当前线程
    Thread current = Thread.currentThread();
    // 拿到state
    int c = getState();
    // 拿到了写锁的低16位标识w
    int w = exclusiveCount(c);
    // c != 0:要么有读操作拿着锁,要么有写操作拿着锁
    if (c != 0) {
        // 如果w == 0,代表没有写锁,拿不到!拜拜!
        // 如果w != 0,代表有写锁,看一下拿占用写锁是不是当前线程,如果不是,拿不到!拜拜!
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 到这,说明肯定是写锁,并且是当前线程持有
        // 判断对低位 + 1,是否会超过MAX_COUNT,超过抛Error
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 如果没超过锁重入次数, + 1,返回true,拿到锁资源。
        setState(c + acquires);
        return true;
    }
    // 到这,说明c == 0
    // 读写锁也分为公平锁和非公平锁
    // 公平:看下排队不,排队就不抢了
    // 走hasQueuedPredecessors方法,有排队的返回true,没排队的返回false
    // 非公平:直接抢!
    // 方法实现直接返回false
    if (writerShouldBlock() ||
        // 以CAS的方式,将state从0修改为 1
        !compareAndSetState(c, c + acquires))
        // 要么不让抢,要么CAS操作失败,返回false
        return false;
    // 将当前持有互斥锁的线程,设置为自己
    setExclusiveOwnerThread(current);
    return true;
}

剩下的addWaiter和acquireQueued和ReentrantLock看的一样,都是AQS自身提供的方法

3.2 写锁-释放锁操作

读写锁的释放操作,跟ReentrantLock一致,只是需要单独获取低16位,判断是否为0,为0就释放成功

// 写锁的释放锁
public final boolean release(int arg) {
    // 只有tryRealse是读写锁重新实现的方法,其他的和ReentrantLock一致
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// 读写锁的真正释放
protected final boolean tryRelease(int releases) {
    // 判断释放锁的线程是不是持有锁的线程
    if (!isHeldExclusively())
        // 不是抛异常
        throw new IllegalMonitorStateException();
    // 对state - 1
    int nextc = getState() - releases;
    // 拿着next从获取低16位的值,判断是否为0
    boolean free = exclusiveCount(nextc) == 0;
    // 返回true
    if (free)
        // 将持有互斥锁的线程信息置位null
        setExclusiveOwnerThread(null);
    // 将-1之后的nextc复制给state
    setState(nextc);
    return free;
}

4、读锁的操作

4.1 读锁的加锁操作

// 读锁加锁操作 
public final void acquireShared(int arg) { 
	// tryAcquireShared,尝试获取锁资源,获取到返回1,没获取到返回-1 
	if (tryAcquireShared(arg) < 0) 
	// doAcquireShared 前面没拿到锁,这边需要排队~ 
	doAcquireShared(arg); 
}

// tryAcquireShared方法 
protected final int tryAcquireShared(int unused) { 
	// 获取当前线程 
	Thread current = Thread.currentThread(); 
	// 拿到state 
	int c = getState();
	 // 那写锁标识,如果 !=0,代表有写锁 
	 if (exclusiveCount(c) != 0 && 
	 // 如果持有写锁的不是当前线程,排队去! 
	 getExclusiveOwnerThread() != current) 
	 // 排队! 
	 return -1;
	  // 没有写锁! 
	  // 获取读锁信息
	   int r = sharedCount(c); 
	   // 公平锁: 有人排队,返回true,直接拜拜,没人排队,返回false 
	   // 非公平锁:正常的逻辑是非公平直接抢,因为是读锁,每次抢占只要CAS成功,必然成功
	    // 这就会出现问题,写操作无法在读锁的情况抢占资源,导致写线程饥饿,一致阻塞………… 
	    // 非公平锁会查看next是否是写锁的,如果是,返回true,如果不是返回false 
	    if (!readerShouldBlock() && 
	    // 查看读锁是否已经达到了最大限制 
	    r < MAX_COUNT && 
	    // 以CAS的方式,对state的高16位+1 
	    compareAndSetState(c, c + SHARED_UNIT)) 
	    { 
	    // 拿到锁资源成功!!! 
	    if (r == 0) { 
	    // 第一个拿到锁资源的线程,用first存储 
	    firstReader = current; 
	    firstReaderHoldCount = 1; 
	    } else if (firstReader == current) { 
	  // 我是锁重入,我就是第一个拿到读锁的线程,直接对firstReaderHoldCount++记录重入的次数 
	    firstReaderHoldCount++; 
	    } else { 
	    // 不是第一个拿到锁资源的 
	    // 先拿到cachedHoldCounter,最后一个线程的重入次数 
	    HoldCounter rh = cachedHoldCounter; 
	    // rh == null: 我是第二个拿到读锁的! 
	    // 或者发现之前有最后一个来的,但是不我,将我设置为最后一个。 
	    if (rh == null || rh.tid != getThreadId(current)) 
	    // 获取自己的重入次数,并赋值给cachedHoldCounter
	     cachedHoldCounter = rh = readHolds.get();
	      // 之前拿过,现在如果为0,赋值给TL 
	      else if (rh.count == 0) 
	      readHolds.set(rh); 
	      // 重入次数+1,
	       // 第一个:可能是第一次拿
	        // 第二个:可能是重入操作 
	        rh.count++; 
	        } 
	        return 1; 
	        } 
	        return fullTryAcquireShared(current); } 

 // 通过tryAcquireShared没拿到锁资源,也没返回-1,就走这 
final int fullTryAcquireShared(Thread current) { 
		HoldCounter rh = null; 
		for (;;) { 
		// 拿state 
		int c = getState(); 
		// 现在有互斥锁,不是自己,拜拜! 
		if (exclusiveCount(c) != 0) { 
		if (getExclusiveOwnerThread() != current) return -1; 
		// 公平:有排队的,进入逻辑。 没排队的,过!
		 // 非公平:head的next是写不,是,进入逻辑。 如果不是,过!
		  } else if (readerShouldBlock()) { 
		  // 这里代码特别乱,因为这里的代码为了处理JDK1.5的内存泄漏问题,修改过~
		   // 这个逻辑里不会让你拿到锁,做被阻塞前的准备
		    if (firstReader == current) { 
		    	// 什么都不做 
		    	} else { if (rh == null) {
		    // 获取最后一个拿到读锁资源的
		     rh = cachedHoldCounter; 
		  if (rh == null || rh.tid != getThreadId(current)) {
		   // 拿到我自己的记录重入次数的。 
		   rh = readHolds.get(); 
		   // 如果我的次数是0,绝对不是重入操作! 
		   if (rh.count == 0) 
		   // 将我的TL中的值移除掉,不移除会造成内存泄漏 
		   readHolds.remove(); 
		   } 
		   } 
		   // 如果我的次数是0,绝对不是重入操作!
		    if (rh.count == 0) 
		    // 返回-1,等待阻塞吧!
		     return -1; 
		     } 
		    } 
		    // 超过读锁的最大值了没? 
		    if (sharedCount(c) == MAX_COUNT)
		     throw new Error("Maximum lock count exceeded"); 
		     // 到这,就CAS竞争锁资源 
		     if (compareAndSetState(c, c + SHARED_UNIT)) { 
		     	// 跟tryAcquireShared一模一样 
		     		if (sharedCount(c) == 0) { firstReader = current; 
		     			firstReaderHoldCount = 1; } 
		     		else if (firstReader == current) { 
		     		firstReaderHoldCount++; } 
		     		else { if (rh == null) 
		     		rh = cachedHoldCounter; 
		     if (rh == null || rh.tid != getThreadId(current)) 
		     rh = readHolds.get(); 
		     else if (rh.count == 0)
		      readHolds.set(rh);
		       rh.count++; 
		       cachedHoldCounter = rh;
		        }
		      return 1;
		     }
		 }
	 }

4.2 加锁-扔到队列准备阻塞操作

// 没拿到锁,准备挂起 
private void doAcquireShared(int arg) { 
		// 将当前线程封装为Node,当前Node为共享锁,并添加到队列的模式 
		final Node node = addWaiter(Node.SHARED); 
		boolean failed = true; 
		try { boolean interrupted = false;
			 for (;;) { 
			 // 获取上一个节点 
			 	final Node p = node.predecessor();
			 	 if (p == head) {
			 	  // 如果我的上一个是head,尝试再次获取锁资源 
			 	  int r = tryAcquireShared(arg);
			 	   if (r >= 0) { 
			 	   // 如果r大于等于0,代表获取锁资源成功 
			 	   // 唤醒AQS中我后面的要获取读锁的线程(SHARED模式的Node) 
			 	   setHeadAndPropagate(node, r); 
			 	   p.next = null; if (interrupted) selfInterrupt(); 
			 	   failed = false; 
			 	   return; 
			 	   }
			 } 
			 // 能否挂起当前线程,需要保证我前面Node的状态为-1,才能执行后面操作
			  if (shouldParkAfterFailedAcquire(p, node) && 
			  //LockSupport.park挂起~~ 
			  parkAndCheckInterrupt()) 
			  interrupted = true; }
			   } finally { 
			   if (failed) cancelAcquire(node);
			    }
			}

四、线程池源码

1、介绍

java构建线程的方式:
	- new Thread
	- new Runnable
	- new Callable
	为了避免频繁创建和销毁线程造成不必要的性能,一般在使用线程时,会采用线程池。
核心线程数的设置方案:
	CPU密集型和IO密集型:
		CPU密集:将核心线程数设置为CPU内核数+1;
		IO密集:将核心线程数设置为CPU内核数*2;
		混合密集型:推荐拆分出两个任务,设置两个线程池,分别执行CPU密集型和IO密集型;
	CPU密集:代表当前线程一直在执行指令。
	IO密集:执行一段指令后,需要通过IO获取结果(调用远程服务,查询数据库)

线程池的使用方式:

public static void main(String[] args) {
        ThreadPoolExecutor eh=new ThreadPoolExecutor(
                1,
                2,
                1,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t=new Thread();
                        return t;
                    }
                },
                new ThreadPoolExecutor.AbortPolicy()
        );
        eh.execute("任务");
        eh.submit("又反回结果的任务");
    }

2、核心属性&状态

	//AtomicInteger就是一个int,写操作用CAS实现,保证原子类
	//ctl维护这线程池的2个核心内容:
	//1、线程池状态(高3位,维护者线程池状态)
	//2、工作线程数量(核心线程+非核心线程,低29位,维护着工作线程个数)
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    // 拿到线程的状态
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
	//拿到工作线程的个数
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }

ctl维护这线程池的2个核心内容:
1、线程池状态(高3位,维护者线程池状态)
2、工作线程数量(核心线程+非核心线程,低29位,维护着工作线程个数)

线程池状态: 在这里插入图片描述

3、execute方法

public void execute(Runnable command) {
		//判空操作
        if (command == null)
            throw new NullPointerException();
         //拿到ctl
        int c = ctl.get();
        //通过ctl获取到当前线程的个数,并判断是否小于核心线程数
        if (workerCountOf(c) < corePoolSize) {
        	// true:代表是核心线程,false:代表是非核心线程
            if (addWorker(command, true))
                return;
            // 如果添加失败,重新获取ctl
            c = ctl.get();
        }
        // 核心线程数已经到了最大值、添加时,线程池状态变为SHUTDOWN/STOP
         // 判断线程池是否是运行状态 && 添加任务到工作队列
        if (isRunning(c) && workQueue.offer(command)) {
        	//拿到ctl
            int recheck = ctl.get();
            //DCL double check
            // 如果状态不是RUNNING,把任务从工作队列移除。
            if (! isRunning(recheck) && remove(command))
            	// 走一波拒绝策略。
                reject(command);
            // 线程池状态是RUNNING。 
            // 判断工作线程数是否是0个。 
            // 可以将核心线程设置为0,所有工作线程都是非核心线程。 
            // 核心线程也可以通过keepAlived超时被销毁,所以如果恰巧核心线程被销毁,也会出现当前效果
            else if (workerCountOf(recheck) == 0)
            // 添加空任务的非核心线程去处理工作队列中的任务
                addWorker(null, false);
        }
        // 可能工作队列中的任务存满了,没添加进去,到这就要添加非核心线程去处理任务
        else if (!addWorker(command, false))
        	//
            reject(command);
    }

4、addWorker添加工作线程

private boolean addWorker(Runnable firstTask, boolean core) {
		//双循环的标签
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            判断线程当前的状态
            if (rs >= SHUTDOWN &&
            		//如果当前线程是SHUTDOWN,还要处理队列中的任务。
            		//如果你添加了工作线程的方式,是任务的非核心线程,并且工作队列中还有任务
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
              //判断工作线程的个数
            for (;;) {
            	//获取工作线程的个数
                int wc = workerCountOf(c);
                // 判断1:工作线程是否已经 == 工作线程最大个数
                // 判断2-true判断:判断是核心线程么?如果是判断是否超过核心线程个数 
                // 判断2-false判断:如果是非核心线程,查看是否超过设置的最大线程数
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                    //CAS操作对线程进行+1
                if (compareAndIncrementWorkerCount(c))
                // +1成功,跳出外层循环,执行添加工作线程的业务 
                // 以CAS方式,对ctl+1,多线程并发操作,只有会有一个成功
                    break retry;
                //重新拿取Ctl
                c = ctl.get(); 
                // 判断线程池状态是否有变化
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }
        // 添加工作线程的业务 
        // 工作线程启动了吗?
        boolean workerStarted = false;
        boolean workerAdded = false;
        // Worker就是工作线程
        Worker w = null;
        try {
        // 创建工作线程,将任务传到Worker中
            w = new Worker(firstTask);
            final Thread t = w.thread;
            // 只有你写的线程工厂返回的是null,这里才会为null
            if (t != null) {
            	//获取锁资源
                final ReentrantLock mainLock = this.mainLock;
                //加锁,我要避免在启动线程的时候,线程池状态发生变化
                mainLock.lock();
                try {
                	//重新获取ctl,拿线程池的状态
                    int rs = runStateOf(ctl.get());
					// DCL i think you know~~~
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
             // 判断Worker中的thread是否已经启动了,一般不会启动,除非你在线程工厂把他启动了
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        //将工作线程放到,hashset中去
                        workers.add(w);
                        // 获取工作线程个数,判断是否需要修改最大工作线程数记录。                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        // 工作线程添加成功 0
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                // 如果添加成功
                if (workerAdded) {
                	// 启动工作线程
                    t.start();
                    // 设置标识为true
                    workerStarted = true;
                }
            }
        } finally {
        	// 如果工作线程启动失败
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

// 如果添加工作线程失败,执行
private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        	// 说明worker可能存放到了workers的hashSet中。
            if (w != null)
            		//移除
                workers.remove(w);
             // 减掉workerCount的数值 -1
            decrementWorkerCount();
            tryTerminate();
        } finally {
            mainLock.unlock();
        }
    }

5、runWorker方法执行类

final void runWorker(Worker w) {
		//获取当前的线
        Thread wt = Thread.currentThread();
        //拿到Worker存放的Runnable
        Runnable task = w.firstTask;
        //将Worker清空
        w.firstTask = null;
        w.unlock(); // allow interrupts
        //这就是一个标识
        boolean completedAbruptly = true;
        try {
        	//如果Worker自身携带任务,直接执行 
        	// 如果Worker携带的是null,通过getTask去工作队列获取任务
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // 判断线程池状态是否大于等于STOP,如果是要中断当前线程
                if ((runStateAtLeast(ctl.get(), STOP) ||
                //DCL 中断当前线程
                     (Thread.interrupted() &&
                     runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                	// 前置钩子
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                    //执行任务
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                    	//后置钩子
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    // 当前工作执行完一个任务,就++
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

6、getTask工作线程排队拿任务

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            // 线程池状态判断 
            // 如果线程池状态为SHUTDOWN && 工作队列为空 
            // 如果线程池状态为STOP
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
			//对数量的判断
            int wc = workerCountOf(c);

            // 判断核心线程是否允许超时?
             // 工作线程个数是否大于核心线程数
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
			// 判断工作线程是否超过了最大线程数 && 工作队列为null
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                // 工作线程数有问题,必须-1,干掉当前工作线程 
                // 工作线程是否超过了核心线程,如果超时,就干掉当前线程
                 // 对工作线程个数--
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
            // 如果是非核心,走poll,拉取工作队列任务, 
            // 如果是核心线程,走take一直阻塞,拉取工作队列任务
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    // 当工作队列没有任务时,这时就会被Condition通过await阻塞线程 
                    // 当有任务添加到工作线程后,这是添加完任务后,就会用过Condition.signal唤醒阻塞的线程
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

7、processWorkerExit工作线程告辞~

private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 如果是不正常操作,需要先对工作线程数-- (如果正常情况,getTask就--了)
        if (completedAbruptly) 
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        // 将当前工作线程完整的任务个数赋值给整个线程池中的任务数
            completedTaskCount += w.completedTasks;
            // 干掉当前工作线程
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
		// 线程池是否可以中止,线程池状态是否发生变化。
        tryTerminate();

        int c = ctl.get();
        //如果当前线程池状态小于STOP
        if (runStateLessThan(c, STOP)) {
        // 判断线程池中的工作队列是否还有任务,并且工作线程是否还在。
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            // 添加非核心空任务的线程处理工作队列中的任务
            addWorker(null, false);
        }
    }

拒绝策略:线程池提供的拒绝策略,一般不适合你的业务场景时,你就自己定义即可。

AbortPolicy:抛出异常!
CallerRunsPolicy:让提交任务的线程处理这个任务!
DiscardPolicy:啥也不做,任务没了!
DiscardOldestPolicy:扔掉队列最前面的任务,尝试把当前任务添加进去!
任务处理流程:

主线程执行execute添加任务,线程池创建工作线程,执行任务,执行任务,再次拉取工作队列任务,直到工作队列没有任务,阻塞工作线程

工作线程阻塞在工作队列,主线程执行execute添加任务到工作队列,工作线程被唤醒,拿到工作队列中的任务执行,执行完毕,再次拉取工作队列任务,直到工作队列没有任务,阻塞工作线程

5、ConcurrentHashMap源码分析

1、结构介绍

HashMap和ConcurrentHashMap存储结构是一样的
ConcurrentHashMap是线程安全的。
存储结构:
在这里插入图片描述
关于put和putIfAbsent的区别

// put和putIfAbsent都是想ConcurrentHashMap中存储值。
 // 如果出现key一致的,将新数据覆盖老数据,并且返回老数据 
 public V put(K key, V value) { 
 	return putVal(key, value, false); 
 } 
 // 如果出现key一致的,什么都不做,返回老数据。 最只有key不存在时,才会正常的添加数据 
 public V putIfAbsent(K key, V value) { 
 	return putVal(key, value, true); 
 }

2、散列算法

final V putVal(K key, V value, boolean onlyIfAbsent) {
		//ConcurrentHashMap中的key和value不能为空,hashMapkey和value可以为空
        if (key == null || value == null) throw new NullPointerException();
        // 散列算法就是基于key进行hash运算,并且根据散列算法的结果,确定当前key-value存储到数组的哪个索引位置。
        int hash = spread(key.hashCode());
//散列算法
//散列算法是为了让Hashcode的高16位参与到索引位置的计算中,从而尽可能的打散数据存到数组上,从而减少hash冲突
// ConcurrentHashMap中,还会将hash值对HASH_BITS进行&运算,让hash值一定是一个正数。 
// 因为ConcurrentHashMap中数组上的数据的hash值,如果为负数,有特殊含义
// static final int MOVED = -1; // 代表当前位置数据在扩容,并且数据已经迁移到了新数组 
// static final int TREEBIN = -2; // 代表当前索引位置下,是一个红黑树。 转红黑树,TreeBin有参构造 
// static final int RESERVED = -3; // 代表当前索引位置已经被占了,但是值还没放进去呢。 compute方法
 static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
    }

00011000 00000110 00111000 00001100 h
^ 00000000 00000000 00011000 00000110 h >>> 16
00011000 00000110 00111000 00001100
& 00000000 00000000 00000111 11111111 2048 - 1
ConcurrentHashMap是如何根据hash值,计算存储的位置? (数组长度 - 1) & (h ^ (h >>> 16))
00011000 00000110 00110000 00001100 key1-hash
00011000 00000110 00111000 00001100 key2-hash
& 00000000 00000000 00000111 11111111 2048 - 1

3、初始化数组

final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //代表当前数组还没有初始化
            if (tab == null || (n = tab.length) == 0)
            	//对当前数组进行初始化
                tab = initTable();
//初始化
 private final Node<K,V>[] initTable() {
 		// 声明tab:临时存数组。 sc:临时存sizeCtl
        Node<K,V>[] tab; int sc;
        //代表当前数组还没有初始化
        while ((tab = table) == null || tab.length == 0) {
        	//sc赋值,并判断sc是否小于0
            if ((sc = sizeCtl) < 0)
            	//线程做让步
                Thread.yield(); 
            // 如果sc大于等于0,没人在执行初始化操作。 
            // 以CAS的方式,将sizeCtl,改为-1,代表当前线程正在执行初始化逻辑
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                	//如果当前数组没有初始化
                    if ((tab = table) == null || tab.length == 0) {					拿到数组的初始化长度
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        //创建数组
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        // 依次给局部变量和成员变量赋值。
                        table = tab = nt;
                        //计算下次扩容的阈值
                        sc = n - (n >>> 2);
                    }
                } finally {
                // 将扩容阈值赋值给sizeCtl
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

4、添加数据-数组

final V putVal(K key, V value, boolean onlyIfAbsent) { 
		int binCount = 0; 
		for (Node<K,V>[] tab = table;;) { 
		// n: 数组长度。 i:索引位置。 f:i位置的数据。 fh:是f的hash值 
		Node<K,V> f; 
		int n, i, fh;
		 // tabAt(数组,索引位置) = 拿到数组指定索引位置的数据 
		 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null{ 
		 // 当前索引位置数据为null。
		  // 以CAS的方式,将数据放到tab的i位置上,将hash,key,value封装成了一个Node对象 
		  if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null))) break; }
		   // 说明当前位置数据已经被迁移到了新数组。
		    else if ((fh = f.hash) == MOVED) 
		    // 帮你扩容,快点扩容完,我好把数据放到新数组~~~ 
		    tab = helpTransfer(tab, f); } return null; }

5、整个put的流程

final V putVal(K key, V value, boolean onlyIfAbsent) {
		//如果ConcurrentHashMap的key和value为空,直接抛异常
        if (key == null || value == null) throw new NullPointerException();
        //通过散列散发计算key的hash值
        int hash = spread(key.hashCode());
        //计算链表的长度
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
        	// n: 数组长度。 i:索引位置。 f:i位置的数据。 fh:是f的hash值
            Node<K,V> f; int n, i, fh;
            //数组还没有初始化
            if (tab == null || (n = tab.length) == 0)
            	//数组初始化
                tab = initTable();
             //如果当前节点的数据为空
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {			
            	//执行cas操作将当前位置添加上Node
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                
            }
            //如果当前的hash值为-1,代表当前数据正在扩容,并且数据已经迁移到了新数组。
            else if ((fh = f.hash) == MOVED)
            // 帮你扩容,快点扩容完,我好把数据放到新数组~~~
                tab = helpTransfer(tab, f);
            else {
            	//旧值
                V oldVal = null;
                //对当前桶进行加锁
                synchronized (f) {
                	// 再判断一次,数据没有变化,正常挂链表
                    if (tabAt(tab, i) == f) {
                    	//添加链表操作
                        if (fh >= 0) {
                        // binCount赋值1,记录链表中Node的长度
                            binCount = 1;
                            // e:暂时指向数组位置数据
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //拿到当前数据的hash值和数组位置hash值做比较
                                //当前数据的key和数组的key做比较,当签数据的key不为空,并且将当前的key和数组的key做equal和==比较
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                     //获取数组的key的value值
                                    oldVal = e.val;
                                    //判断是否进行覆盖
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                //将当前数据的Node赋值为上一个节点
                                Node<K,V> pred = e;
                                	// e指向下一个节点,并且如果e == null,说明下面没节点了
                                if ((e = e.next) == null) {
                                // 将当前的值封装为Node对象,并挂在最后一个节点的后面
                                    pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                    break;
                                }
                            }
                        }
                        //判断当前节点的数据是否为TreeBin类型
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                //如果binCount不等于0
                if (binCount != 0) {
                	//判断当前的bincount是否大于8
                    if (binCount >= TREEIFY_THRESHOLD)
                    // 判断是扩容还是转红黑树
                        treeifyBin(tab, i);
                    //如果旧的值不为空,就直接返回旧的值
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

6、触发扩容

private final void treeifyBin(Node<K,V>[] tab, int index) {
		//b:当前节点的数据 n数组的大小 sc:sizeCtl
        Node<K,V> b; int n, sc;
        //判断数组是否为null
        if (tab != null) {
        	//判断数组的长度是否小于64
            if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
            	// 扩容前的一些准备和业务判断
                tryPresize(n << 1);
            // 转红黑树操作 
            // 将单向链表转换为TreeNode对象(双向链表),再通过TreeBin方法转为红黑树。 
            // TreeBin中保留着双向链表以及红黑树!
            else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
            	//对当前的b加锁
                synchronized (b) {
                	//DCL double check操作
                    if (tabAt(tab, index) == b) {
                        TreeNode<K,V> hd = null, tl = null;
                        for (Node<K,V> e = b; e != null; e = e.next) {
                            TreeNode<K,V> p =
                                new TreeNode<K,V>(e.hash, e.key, e.val,
                                                  null, null);
                            if ((p.prev = tl) == null)
                                hd = p;
                            else
                                tl.next = p;
                            tl = p;
                        }
                        setTabAt(tab, index, new TreeBin<K,V>(hd));
                    }
                }
            }
        }
    }

6、ConcurrentHashMap扩容

三种触发方式

达到了扩容的阈值

1、tryPreSize-初始化数组

private final void tryPresize(int size) {
		// 这个判断是给putAll留的,要计算当前数组的长度(初始化)
		 // 如果size大于最大长度 / 2,直接将数组长度设置为最大值。 
		 // tableSizeFor,将长度设置的2的n次幂 
		 // c是初始化数组长度
        int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
            tableSizeFor(size + (size >>> 1) + 1);
         // sc是给sizeCtl赋值 // -1:正在初始化阶段,//小于-1:代表正在扩容,等于0代表还没有初始化,等于1,不确定是没有初始化,还是初始化完成了
        int sc;
        //代表此时数组的状态没有初始化或者已经初始化完成
        while ((sc = sizeCtl) >= 0) {
            Node<K,V>[] tab = table; int n;
            //判断当前数组还没有初始化
            if (tab == null || (n = tab.length) == 0) {
            	//给初始化数组长度赋值
                n = (sc > c) ? sc : c;
                //利用cas使得当前的sc=-1代表正在初始化
                if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                    try {
                        if (table == tab) {
                            @SuppressWarnings("unchecked")
                            // 初始化数组
                            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                            // 初始化数组赋值给成员变量
                            table = nt;
                            //设置阈值 
                            sc = n - (n >>> 2);
                        }
                    } finally {
                    //代表当前数组的阈值
                        sizeCtl = sc;
                    }
                }
            }
            //要么是c没有超过阈值,要么是超过最大值,啥事不做~~~
            else if (c <= sc || n >= MAXIMUM_CAPACITY)
                break;
               //DCL
            else if (tab == table) {
            	// 计算扩容标识戳(基于老数组长度计算扩容标识戳,因为ConcurrentHashMap允许多线程迁移数据。)
                int rs = resizeStamp(n);
                //这地方是一个bug永远也不会进来了
                //表示当前是正在扩容或者正在初始化阶段
                if (sc < 0) {
                    Node<K,V>[] nt;
                    // 判断协助扩容线程的标识戳是否一致
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 // BUG之一,在判断扩容操作是否已经到了最后的检查阶段
                    	||sc == rs + MAX_RESIZERS // BUG之一,判断扩容线程是否已经达到最大值
                        || (nt = nextTable) == null ||
                        transferIndex <= 0)// transferIndex为线程领取任务的最大节点,如果为0,代表所有老数据迁移任务都没领干净了
                        break;
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                // 还没有执行扩容,当前线程可能是第一个进来执行扩容的线程 
                // 基于CAS的方式,将sizeCtl从原值改为 扩容标识戳左移16位
             	 // 10000000 00011010 00000000 00000010 一定是< -1的负数,可以代表当前ConcurrentHashMap正在扩容 
             	 // 为什么是低位+2,代表1个线程扩容。 低位为5,就代表4个线程正在并发扩容 
             	 // 扩容分为2部:创建新数组,迁移数据。 
             	 // 当最后一个线程迁移完毕数据后,对低位-1.最终结果低位还是1,需要对整个老数组再次检查,数据是否迁移干
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                 // 开始扩容操作,传入老数组~~
                    transfer(tab, null);
            }
        }
    }

2、transfer

transfer方法:
	计算步长
	初始化新数组
	线程领取迁移数据任务
	判断迁移是否完成,并判断当前线程是否是最后一个完成的
	查看当前位置数据是否为null
	查看当前位置数据是否为fwd
	链表迁移数据-lastRun机制
	红黑树迁移-迁移完数据长度小于等于6,转回链表
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
		// 创建新数组流程! 
		// n:老数组长度32, stride:扩容的步长16
        int n = tab.length, stride;
        // NCPU:4 
        // 00000000 00000000 00000000 00000000 
        // 00000000 00000000 00000100 00000000 - 1024 512 256 128 / 4 = 32
         // 如果每个线程迁移的长度基于CPU计算,大于16,就采用计算的值,如果小于16,就用16 
         // 每个线程每次最小迁移16长度数据
          // stride = 1 < 16 
          // 这个操作就是为了充分发挥CPU性能,因为迁移数据是CPU密集型操作,尽量让并发扩容线程数量不要太大,从而造成CPU的性能都消耗在了切换上,造成扩容效率降低 
          // 如果要做优化的,推荐将扩容线程数设置为和CPU内核数+1一致。
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; 
          //判断新数组是否初始化
        if (nextTab == null) { 
            try {
                @SuppressWarnings("unchecked")
                //初始化数组
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];			//将数组赋值给新数组
                nextTab = nt;
                 // 要么OOM,要么数组长度达到最大值。
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
           
            nextTable = nextTab;
            // transferIndex设置为老数组长度
            transferIndex = n;
        }
        //新数组长度
        int nextn = nextTab.length;
        // 先看领取任务的过程!!! 
        // 声明fwd节点,在老数组迁移数据完成后,将fwd赋值上去
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        // 领任务的核心标识
        boolean advance = true;
        // 扩容结束了咩?
        boolean finishing = false; 
        //扩容的for循环
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            //领取任务的while循环
            while (advance) {
                int nextIndex, nextBound;
                // 第一个判断是为了迁移下一个索引数据(暂时不管)
                if (--i >= bound || finishing)
                    advance = false;
                  //说明没有任务可以领取了
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                // transferIndex:16 
                // stride:16,nextIndex:32,nextBound:16 
                // bound:16,i:31 
                // 开始领取任务,如果CAS成功,代表当前线程领取了32~16这个范围数据的迁移
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            // 迁移最后一段的线程干完活了,或者其他线程没有任务可以领取了。 
            if (i < 0) { 
            	int sc;
            	 // 判断结束了没,第一次肯定进不来 
            	 if (finishing) {
            	  // 结束扩容,将nextTabl设置为null 
            	  nextTable = null;
            	   // 将迁移完数据的新数组,指向指向的老数组 
            	   table = nextTab;
            	    // 将sizeCtl复制为下次扩容的阈值 
            	    sizeCtl = (n << 1) - (n >>> 1); 
            	    // 结束 
            	    return; 
            	  }
            	  // 到这,说明当前线程没有任务可以领取了
            	   // 基于CAS的方式,将低位-1,代表当前线程退出扩容操作(如果是最后一个,还有一个额外的活)
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                	// 判断我是否是最后一个完成迁移数据的线程,如果不是,直接return结束
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                        // 如果到这,说明我是最后一个结束迁移数据的线程。 
                        // finishing结束表示和advance领取任务的标识全部设置为true
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            else if ((f = tabAt(tab, i)) == null)
            	// 如果发现迁移为主的数据为null,设置放置一个fwd,代表当前位置迁移完成
                advance = casTabAt(tab, i, null, fwd);
                // 是在检查时的逻辑MOVED代表当前已经扩容完成了
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            else {
            	//对当前的桶加锁
                synchronized (f) {
                	//拿到当前位置的数据
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        // 说明当前节点状态正常,不是迁移,不是红黑树,不是预留
                        if (fh >= 0) {
                        // fh与老数组进行&运算,得到runBit 
                        // 00001111 
                        // 00010000 
                        // 这个计算的结果,会决定当前数据在迁移时,是放到新数组的i位置还有新数组的 i + n位置
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            // lastRun机制 
                            // 提前循环一次链表,将节点赋值到对应的高低位Node./ 
                            // 如果链表最后面的值没有变化,那就不动指针,直接复制。
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            // 再次循环时,就循环到lastRun位置,不再继续往下循环 
                            // 这样可以不用每个节点都new,避免GC和OOM问题。
                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }
                            //放低位
                            setTabAt(nextTab, i, ln);
                            //放高位
                            setTabAt(nextTab, i + n, hn);
                            // 将当前迁移完的桶位置,设置上fwd,代表数据迁移完毕
                            setTabAt(tab, i, fwd);
                            // advance,代表执行下次循环,i--。
                            advance = true;
                        }
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                    (h, e.key, e.val, null, null);
                                if ((h & n) == 0) {
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    else
                                        loTail.next = p;
                                    loTail = p;
                                    ++lc;
                                }
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

6、helpTransfer方法-协助扩容

// 协助扩容 
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) { 	
		Node<K,V>[] nextTab; int sc; 
		// 老数组不为null,当前节点是fwd,新数组不为null 
		if (tab != null && (f instanceof ForwardingNode) && 
		(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) 
		{ 
			// 创建自己的扩容标识戳 
			int rs = resizeStamp(tab.length); 
			// 判断之前赋值的内容是否有变化,并且sizeCtl是否小于0
			 while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) { if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || transferIndex <= 0) 
			 // 有一个满足,就说明不需要协助扩容了 break; 
			 // CAS,将sizeCtl + 1,代表来协助扩容了
			  if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
			   transfer(tab, nextTab); break; } 
			   } 
			   return nextTab; 
			   }
			    return table;
			     }

7、JUC并发工具

1、CountDownLatch应用

CountDownLatch本身就好像一个计数器,可以让一个线程或多个线程等待其他线程完成后再执行。

应用方式

public static void main(String[] args) throws InterruptedException, BrokenBarrierException { 
	// 声明CountDownLatch,有参构造传入的值,会赋值给state,CountDownLatch基于AQS实现 
	// 3 - 1 = 2 - 1 = 1 - 1 
	CountDownLatch countDownLatch = new CountDownLatch(3);
	 new Thread(() -> { System.out.println("111"); countDownLatch.countDown(); }).start(); 
	 new Thread(() -> { System.out.println("222"); countDownLatch.countDown(); }).start(); 
	 new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("333"); countDownLatch.countDown(); }).start();
	  // 主线会阻塞在这个位置,直到CountDownLatch的state变为0 
	  countDownLatch.await(); 
	  System.out.println("main"); }

2、CountDownLatch核心源码分析

构造方法:

// CountDownLatch 的有参构造 
public CountDownLatch(int count) { 
// 健壮性校验 
	if (count < 0) throw new IllegalArgumentException("count < 0"); 
	// 构建Sync给AQS的state赋值
	 this.sync = new Sync(count); 
}

countDown方法:

// countDown方法,本质就是调用了AQS的释放共享锁操作 
// 这里的功能都是AQS提供的,只有tryReleaseShared需要实现的类自己去编写业务 
public final boolean releaseShared(int arg) { 
	if (tryReleaseShared(arg)) { 
	// 唤醒在AQS队列中排队的线程。 
	doReleaseShared(); 
	return true; 
	} 
	return false;
	 }
 // countDownLatch实现的业务
  protected boolean tryReleaseShared(int releases) { 
  		for (;;) { 
  			int c = getState(); 
  			if (c == 0) return false; 
  			// state - 1
  			 int nextc = c-1; 
  			 // 用CAS赋值 
  			 if (compareAndSetState(c, nextc)) 
  			 	return nextc == 0; } } 
  	// 如果CountDownLatch中的state已经为0了,那么再次执行countDown跟没执行一样。 
  	// 而且只要state变为0,await就不会阻塞线程。

await方法:

// await方法 
public void await() throws InterruptedException { 
	// 调用了AQS提供的获取共享锁并且允许中断的方法 
	sync.acquireSharedInterruptibly(1);} 
	// AQS提欧的获取共享锁并且允许中断的方法 
	public final void acquireSharedInterruptibly(int arg) throws InterruptedException { 
	if (Thread.interrupted()) throw new InterruptedException(); 
	// countDownLatch操作
		 if (tryAcquireShared(arg) < 0) 
		 // 如果返回的是-1,代表state肯定大于0 	
		 	doAcquireSharedInterruptibly(arg); 
		 	} 
		 	// CountDownLatch实现的tryAcquireShared 
		 	protected int tryAcquireShared(int acquires) { 
		 	// state为0,返回1,。否则返回-1 
		 	return (getState() == 0) ? 1 : -1; } 
		 	// 让当前线程进到AQS队列,排队去
		 	 private void doAcquireSharedInterruptibly(int arg) 
		 	 	throws InterruptedException { 
		 	 	// 将当前线程封装为Node,并且添加到AQS的队列中 
		 	 	final Node node = addWaiter(Node.SHARED); 
		 	 	boolean failed = true; 
		 	 	try { for (;;) { 
		 	 	final Node p = node.predecessor();
		 	 	 if (p == head) { 
		 	 	 // 再次走上面的tryAcquireShared,如果返回的是的1,代表state为0 
		 	 	 int r = tryAcquireShared(arg); 
		 	 	 if (r >= 0) { 
		 	 	 // 会将当前线程和后面所有排队的线程都唤醒。
		 	 	  setHeadAndPropagate(node, r); 
		 	 	  p.next = null; 
		 	 	  // help GC 
		 	 	  failed = false;
		 	 	   return; } } 
		 	 	 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } 
		 	 	 finally { if (failed) cancelAcquire(node); } }

3、Semaphore应用

	也是常用的JUC并发工具,一般用于流控。比如有一个公共资源,多线程都可以访		问时,可以用信号量做限制。
	连接池,内部的链接对象有限,每当有一个线程获取连接对象时,对信号量-1,当这个线程归还资源时对信号量+1。
	如果线程拿资源时,发现Semaphore内部的资源个数为0,就会被阻塞。

Semaphore是实例

// Semaphore有公平和非公平两种竞争资源的方式。 
public Semaphore(int permits, boolean fair) { 
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
 } 
 // 设置资源个数,State其实就是信号量的资源个数 
 Sync(int permits) { setState(permits); 
 }
// 阿巴阿巴~ 
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { 
	if (Thread.interrupted()) throw new InterruptedException(); 
	if (tryAcquireShared(arg) < 0) 	
		doAcquireSharedInterruptibly(arg);
		 }
// 公平 
protected int tryAcquireShared(int acquires) {
	 for (;;) {
	  // 公平方式,先好看队列中有木有排队的,有排队的返回-1,执行doAcquireSharedInterruptibly去排队 
	  if (hasQueuedPredecessors()) 
	  return -1; 
	  // 那state 
	  int available = getState(); 
	  // remaining = 资源数 - 1
	   int remaining = available - acquires; 
	   // 如果资源不够,直接返回-1 
	   if (remaining < 0 || 
	   // 如果资源够,执行CAS,修改state 
	   compareAndSetState(available, remaining)) 
	   return remaining; } }
// 非公平
 final int nonfairTryAcquireShared(int acquires) {
 	 for (;;) { 
 	 	int available = getState(); 
 	 	int remaining = available - acquires; 
 	 	if (remaining < 0 || compareAndSetState(available, remaining)) 
 	 		return remaining; } }

release:

// 两个一起 阿巴阿巴 
public void release() { 
	sync.releaseShared(1); } 
public final boolean releaseShared(int arg) { 
	if (tryReleaseShared(arg)) { 
		// 唤醒在AQS中排队的Node,去竞争资源 
			doReleaseShared(); 
			return true; } 
	return false;
 }
// 信号量实现的归还资源
 protected final boolean tryReleaseShared(int releases) {
 		 for (;;) {
 		  // 拿state 
 		  	int current = getState(); 
 		  	// state + 1 
 		  	int next = current + releases;
 		  	 // 资源最大值,再+1,变为负数
 		  	if (next < current) throw new Error("Maximum permit count exceeded"); 
 		  	 // CAS 改一手 
 		  	if (compareAndSetState(current, next)) return true; } }

分析AQS中PROPAGATE类型节点(唯一的难点):
JDK1.5中,使用信号量时,可能会造成在有资源的情况下,后继节点无法被唤醒。
在这里插入图片描述
在JDK1.8中,问题被修复,修复方式就是追加了PROPAGATE节点状态来解决。

共享锁在释放资源后,如果头节点为0,无法确认真的没有后继节点。如果头节点为0,需要将头节点的状态修改为-3,当最新拿到锁资源的线程,查看是否有后继节点并且为共享锁,就唤醒排队的线程
五、CyclicBarrier应用:
一般称为栅栏,和CountDownLatch很像。

CountDownLatch在操作时,只能使用一次,也就是state变为0之后,就无法继续玩了。

CyclicBarrier是可以复用的,他的计数器可以归位,然后再处理。而且可以在计数过程中出现问题后,重置当前CyclicBarrier,再次重新操作!

应用一波

public static void main(String[] args) throws InterruptedException, BrokenBarrierException { 
	// 声明栅栏 
	CyclicBarrier barrier = new CyclicBarrier(3,() -> { System.out.println("打手枪!"); }); 
	new Thread(() -> { System.out.println("第一位选手到位");
	 try { barrier.await(); System.out.println("第一位往死里跑!"); } catch (Exception e) { e.printStackTrace(); } }).start(); 
	 new Thread(() -> { System.out.println("第二位选手到位"); try { barrier.await(); System.out.println("第二位也往死里跑!"); } catch (Exception e) { e.printStackTrace(); } }).start(); 
	 System.out.println("裁判已经到位"); 
	 barrier.await(); }

await:
线程执行await方法,会对count-1,再判断count是否为0

如果不为0,需要添加到AQS中的ConditionObject的Waiter队列中排队,并park当前线程

如果为0,证明线程到齐,需要执行nextGeneration,会先将Waiter队列中的Node全部转移到AQS的队列中,并且有后继节点的,ws设置为-1。没有后继节点设置为0。然后重置count和broker标记。等到unlock执行后,每个线程都会被唤醒。

// 选手到位!!!
 private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { 
 	// 加锁?? 因为CyclicBarrier是基于ReentrantLock-Condition的await和singalAll方法实现的。
 	 // 相当于synchronized中使用wait和notify 
 	 // 别忘了,只要挂起,会释放锁资源。 
 	 final ReentrantLock lock = this.lock; 
 	 lock.lock();
 	  try { 
 	 // 里面就是boolean,默认false 
 	 	final Generation g = generation;
 	 	 // 判断之前栅栏加入线程时,是否有超时、中断等问题,如果有,设置boolean为true,其他线程再进来,直接凉凉 
 	 	 if (g.broken) throw new BrokenBarrierException(); 
 	 	 if (Thread.interrupted()) { breakBarrier(); throw new InterruptedException(); } 
 	 	 // 对计数器count-- 
 	 	 int index = --count; 
 	 	 // 如果--完,是0,代表突破栅栏,干活!
 	 	  if (index == 0) { 
 	 	  // 默认false
 	 	   boolean ranAction = false; 
 	 	   try { 
 	 	   		// 如果你用的是2个参数的有参构造,说明你传入了任务,index == 0,先执行CyclicBarrier有参的任务
 	 	   		 final Runnable command = barrierCommand; 
 	 	   		 if (command != null) command.run();
 	 	   		  // 设置为true 
 	 	   		  ranAction = true; nextGeneration(); 
 	 	   		  return 0; } finally { 
 	 	   		  if (!ranAction) breakBarrier(); } }
 	 	   		   // --完之后,index不是0,代表还需要等待其他线程 
 	 	   		   for (;;) { try { 
 	 	   		   // 如果没设置超时时间。 await()
 	 	   		    if (!timed) trip.await(); 
 	 	   		    // 设置了超时时间。 
 	 	   		    	await(1,SECOND)
 	 	   		     else if (nanos > 0L) 
 	 	   		     	nanos = trip.awaitNanos(nanos); 
 	 	   		     	} catch (InterruptedException ie) { 
 	 	   		     	if (g == generation && ! g.broken) {
 	 	   		     		 breakBarrier(); throw ie; } else { Thread.currentThread().interrupt(); } } 
 	 	   		     		 if (g.broken) throw new BrokenBarrierException();
 	 	   		     		  if (g != generation) 
 	 	   		     		  		return index;
 	 	   		     		   if (timed && nanos <= 0L) { 
 	 	   		     		   		breakBarrier(); 
 	 	   		     		   		]throw new TimeoutException(); } } } finally { lock.unlock(); } } 
// 挂起线程 
public final void await() throws InterruptedException { 
	// 允许中断
	 if (Thread.interrupted()) throw new InterruptedException(); 
	 // 添加到队列(不是AQS队列,是AQS里的ConditionObject中的队列) 
	 Node node = addConditionWaiter();
	  int savedState = fullyRelease(node); 
	  int interruptMode = 0; 
	  while (!isOnSyncQueue(node)) { 
	  	// 挂起当前线程 
	  	LockSupport.park(this);
	  	 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } }
	  	 	 // count到0,唤醒所有队列里的线程线程 
	  private void nextGeneration() { 
	  		// 这个方法就是将Waiter队列中的节点遍历都扔到AQS的队列中,真正唤醒的时机,是unlock方法 
	  		trip.signalAll(); 
	  		// 重置计数器
	  		 count = parties; 
	  		 // 重置异常判断
	  		  generation = new Generation(); }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值