多线程并发总结

文章目录

  • 一、Synchronized
    • 1.1、原子性
    • 1.2 有序性
    • 1.3 可见性
    • 2、synchronized使用
    • 3、synchronized锁升级
  • 二、ReentrantLock源码
    • 1、ReentrantLock介绍
    • 2、ReentranctLock的lock方法源码
    • 3、ReentranctLock的acquire方法源码
    • 4、ReentranctLock的tryAcquire方法源码
    • 5、ReentranctLock的addWaiter方法源码
    • 6、ReentranctLock的Acquirequeue方法源码
    • 7、ReentranctLock的unlock方法源码
  • 三、ReentrantReadWriteLock读写锁源码
    • 1、为什末要出现读写锁
    • 2、读写锁的核心思想
    • 3、写锁的操作
      • 3.1 、写锁加锁-acquire
      • 3.2 写锁-释放锁操作
    • 4、读锁的操作
      • 4.1 读锁的加锁操作
      • 4.2 加锁-扔到队列准备阻塞操作

一、Synchronized

1.1、原子性

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

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

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

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

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

锁:synchronized,lock,Atomic(CAS)
多线程并发总结_第2张图片
使用lock锁也会有类似的概念,也就是在操作i++的三个指令前,先基于AQS成功修改state后才可以操作

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

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

CAS:compare and swap

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

CAS就是比较和交换,而比较和交换是一个原子操作
多线程并发总结_第3张图片
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是如何基于对象实现的互斥锁,先了解对象再内存中是如何存储的。
多线程并发总结_第4张图片
多线程并发总结_第5张图片

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);
			    }
			}

你可能感兴趣的:(java基础,java,开发语言)