Java并发-锁相关知识

LockSupport工具类

LockSupport是个工具类,是使用Unsafe类实现的,它的主要作用是挂起和唤醒线程,该工具类是创建锁和其他同步类的基础。
LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的。

park()方法

如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.park()时会马上返回,否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起。

什么时候才park处返回?

  1. 其他线程调用unpark(Thread thread)方法并且将当前线程作为参数时。
  2. 其他线程设置了park线程的中断标志为true。(调用park()方法而被阻塞的线程被其他线程中断而返回时并不会抛出InterruptedException异常)
  3. 虚假唤醒,所以在调用park方法时最好也使用循环条件判断方式。

unpark(Thread thread)方法

当一个线程调用unpark 时,如果参数thread线程没有持有thread与LockSupport类关联的许可证,则让 thread 线程持有,许可证与park的唤醒有关,有以下两种情况:

  1. 如果thread之前因调用park()而被挂起,则调用unpark后,该线程会被唤醒。
  2. 如果thread之前没有调用park,则调用unpark方法后,再调用park 方法,其会立刻返回。

parkNanos(liong nanos)方法

和park方法类似,如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.parkNanos(long nanos)方法后会马上返回。该方法的不同在于,如果没有拿到许可证,则调用线程会被挂起nanos时间后修改为自动返回。

park(Object blocker)方法

Thread类里面有个变量volatile Object parkBlocker,用来存放park方法传递的blocker对象,也就是把blocker变量存放到了调用park方法的线程的成员变量里面。
调用带blocker参数的方法void park(Object blocker)方法,当线程在没有持有许可证的情况下调用park方法而被阻塞挂起时,这个blocker对象会被记录到该线程内部,一般使用的时候blocker都会被设置为this,这样使用诊断工具可以观察线程被阻塞的原因时就能知道是哪个类被阻塞了,因为诊断工具是通过调用getBlocker(Thread)方法来获取blocker对象的。

AQS抽象同步队列

AbstractQueuedSynchronizer抽象同步队列简称AQS,它是JUC下许多并发工作类的底层原理,像并发包中锁的底层就是使用AQS实现的。
在这里插入图片描述
AQS是一个FIFO的双向队列,其内部通过节点head和tail记录队首和队尾元素,队列元素的类型为Node,属性usfafe记录了Unsafe工具类实例,其父类AbstractOwnableSynchronizer的exclusiveOwnerThread属性是在独占锁的时候记录获得锁的线程。
比较重要的是state,它是AQS的状态信息,可以通过getState、setState、compareAndSetState函数修改其值,在不同的工具类中state有不同的作用,比如:

  • 对于ReentrantLock的实现来说,state可以用来表示当前线程获取锁的可重入次数﹔
  • 对于读写锁ReentrantReadWriteLock来说,state的高16位表示读状态,也就是获取该读锁的次数,低16位表示获取到写锁的线程的可重入次数;
  • 对于semaphore来说,state用来表示当前可用信号的个数﹔
  • 对于CountDownlatch来说,state用来表示计数器当前的值;

对于AQS来说,线程同步的关键是对状态值state进行操作。

Node

Node是AQS中的静态内部类,没有获得锁的线程的线程都会被封装成为一个Node节点放到AQS同步队列中,Node结构:

static final class Node {
    //EXCLUSIVE用来标记线程是获取独占资源时被挂起后放入AQS 队列的
    static final Node EXCLUSIVE = null;
    //等待状态
    static final int CANCELLED =  1;
    static final int SIGNAL    = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;
    //waitStatus记录当前线程等待状态,可以为CANCELLED(线程被取消了)、SIGNAL(线程需要被唤醒)、CONDITION(线程在条件队列里面等待)、PROPAGATE(释放共享资源时需要通知其他节点)
    volatile int waitStatus;
    //prev记录当前节点的前驱节点
    volatile Node prev;
    //next记录当前节点的后继节点
    volatile Node next;
    //记录被阻塞的线程
    volatile Thread thread;
}

独占获取和释放

如果state操作权只属于一个线程,则这个锁就是独占的,与独占锁相关的AQS方法有:

  • void acquire(int arg)
  • boolean release(int arg)

独占获取

独占方式获取的资源是与具体线程绑定的,就是说如果一个线程获取到了资源,就会标记是这个线程获取到了,其他线程再尝试操作state获取资源时会发现当前该资源不是自己持有的,就会在获取失败后被阻塞。

// 当一个线程调用acquire(int arg)方法获取独占资源时,会首先使用tryAcquire方法尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败会将当前线程封装为类型为Node.EXCLUSIVE的Node节点后插入到AQS阻塞队列的尾部,并调用LockSupport.park(this)方法挂起自己。
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
//AQS中没有具体的实现,由继承了AQS的子类实现具体操作,主要是对state进行操作,返回true表示拿到锁,返回false表示没有拿到锁。
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
// 入队操作
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) {
            // 如果AQS的head和tail都是null,就要先进行初始化
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // CAS + 自旋把node添加到队尾
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
// 阻塞线程
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

独占释放

当一个线程调用release(int arg)方法时会尝试使用tryRelease操作释放资源,这里是设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面被阻塞的一个线程(thread)。
被激活的线程则使用tryAcquire尝试,看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并被挂起。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
// 由子类实现对state的操作,释放成功返回true,反之为false。
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}
// LockSupport.unpark(thread)方法激活AQS队列里面被阻塞的一个线程。
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

共享获取和释放

对应共享方式的资源与具体线程是不相关的,当多个线程去请求资源时通过CAS方式竞争获取资源,当一个线程获取到了资源后,另外一个线程再次去获取时如果当前资源还能满足它的需要,则当前线程只需要使用CAS方式进行获取即可。
在共享方式下获取和释放资源的方法为:

  • void acquireShared(int arg)
  • boolean releaseShared(int arg)

共享获取

线程调用acquireShared(int arg)获取共享资源时,会首先使用tryAcquireShared尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败则将当前线程封装为类型为Node.SHARED的Node节点后插入到AQS同步队列的尾部,并使用LockSupport.park(this)方法挂起自己。

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

共享释放

当一个线程调用releaseShared(int arg)时会尝试使用tryReleaseShared操作释放资源,这里是设置状态变量state的值,然后使用LockSupport.unpark (thread)激活AQS队列里面被阻塞的一个线程(thread)。
被激活的线程则使用tryReleaseShared查看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并被挂起。

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

条件变量的支持

AQS中Condition条件变量的signal和 await方法可以用来配合锁(使用AQS实现的锁)实现线程间同步的基础设施。
如果获取到锁的线程又调用了对应的条件变量的await()方法,则该线程会释放获取到的锁,并被转换为Node节点插入到条件变量对应的条件队列里面。
这时候因为调用lock.lock()方法被阻塞到AQS 队列里面的一个线程会获取到被释放的锁,如果该线程也调用了条件变量的await()方法则该线程也会被放入条件变量的条件队列里面。
当另外一个线程调用条件变量的signal()或者signalAll()方法时,会把条件队列里面的一个或者全部Node节点移动到AQS的阻塞队列里面,等待时机获取锁。
一个锁对应一个AQS阻塞队列,对应多个条件变量,每个条件变量有自己的一个条件队列。
在这里插入图片描述

ReentrantLock

ReentrantLock是基于AQS实现的独占锁,它通过传入参数来决定内部是公平或者是非公平锁(默认是非公平的):

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

加锁与解锁

ReentrantLockde的加锁和解锁是通过其内部的Sync实例的lock和release方法来实现的,在ReentrantLock中的Sync有公平和非公平两种实现。

private final Sync sync;

public void lock() {
    sync.lock();
}
public void unlock() {
    sync.release(1);
}

Sync

/**
 * Sync继承了AQS,实现了有关独占锁的方法
 */
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    /**
     * 由子类实现
     */
    abstract void lock();

    /**
     * 默认加锁是非公平锁的加锁方式
     */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
             // 无论如何,进来第一步就是尝试用CAS去获取锁
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 拿到锁的是当前线程,state增加
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

    //释放锁,释放锁的时候修改state不用CAS,是因为这是一个独占锁,只有一个线程可以执行释放操作。
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        //判断当前线程是不是锁的持有者
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        //锁是不是已经没有线程占用
        boolean free = false;
        //当state为0
        if (c == 0) {
            free = true;
            //设置没有线程获得该锁,指向为null
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

    //判断获得锁的线程是不是当前线程
    protected final boolean isHeldExclusively() {
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    //获得条件对象
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    // 判断是否锁已经被获取
    final boolean isLocked() {
        return getState() != 0;
    }

}

/**
 * 非公平锁的实现
 */
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * 调用了AQS的acquire方法,acquire方法会调用tryAcquire方法
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

/**
 * 公平锁的实现
 */
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    // 调用了AQS的acquire方法,acquire方法会调用tryAcquire方法
    final void lock() {
        acquire(1);
    }

    /**
     * 公平锁和非公平锁不一样,不会一进方法就调用CAS去尝试拿锁,会用hasQueuedPredecessors判断是否队列还有其他线程等待,如果有,当前线程直接进入AQS同步队列阻塞
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

因此ReentrantLock公平和非公平的区别只是在进入tryAcquire方法的时候会不会去判断AQS同步队列中有没有线程的区别,公平锁会去判断,而非公平锁不会去判断。非公平并不是说队列靠后的Node可以先于前面的Node拿到锁

ReentrantReadWriteLock

在实际中经常会有写少读多的场景,显然ReentrantLock满足不了这个需求,所以ReentrantReadWriteLock应运而生。ReentrantReadWriteLock 采用读写分离的策略,允许多个线程可以同时获取读锁。读写锁的内部维护了一个ReadLock 和一个 WriteLock,它们依赖Sync实现具体功能。而Sync继承自AQS,并且也提供了公平和非公平的实现。ReentrantReadWriteLock巧妙地使用AQS的state属性,用高16位表示读状态,也就是获取到读锁的次数﹔用低16位表示获取到写锁的线程的可重入次数。

读锁加锁

//加读锁
reentrantReadWriteLock.readLock().lock();

//返回ReentrantReadWriteLock中的读锁
public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

public void lock() {
 //调用AQS的acquireShared
  sync.acquireShared(1);
}

public final void acquireShared(int arg) {
	//最后会调用RenntrantReadWriteLock中Sync的方法
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

protected final int tryAcquireShared(int unused) {
	  //获得当前线程
      Thread current = Thread.currentThread();
      //获得state
      int c = getState();
      //exclusiveCount可以获得低16位的值,该值不为0则表示以及有线程获得了写锁,进一步通过getExclusiveOwnerThread方法获得当前的独占线程。
      //这一步如果有写锁,并且不是当前加锁,就会返回-1
      if (exclusiveCount(c) != 0 &&
          getExclusiveOwnerThread() != current)
          return -1;
      //获得读锁的数量
      int r = sharedCount(c);
      //readerShouldBlock返回true说明有线程正在获得写锁
      if (!readerShouldBlock() &&
      	  //不到读锁最大的限制
          r < MAX_COUNT &&
          //通过CAS修改读锁的数量
          compareAndSetState(c, c + SHARED_UNIT)) {
          //在线程获取读锁之前,读锁数量为0,表示当前线程是第一个获得读锁的线程,进行记录
          if (r == 0) {
              firstReader = current;
              firstReaderHoldCount = 1;
          } else if (firstReader == current) {
              firstReaderHoldCount++;
          } else {
              HoldCounter rh = cachedHoldCounter;
              if (rh == null || rh.tid != getThreadId(current))
                  cachedHoldCounter = rh = readHolds.get();
              else if (rh.count == 0)
                  readHolds.set(rh);
              rh.count++;
          }
          return 1;
      }
      // 如果CAS加锁失败,就会进行自选加锁
      return fullTryAcquireShared(current);
  }

//与tryAcquireShared逻辑类似,只不过会自选加锁
final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        } else if (readerShouldBlock()) {
            // Make sure we're not acquiring read lock reentrantly
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            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; // cache for release
            }
            return 1;
        }
    }
}

readerShouldBlock方法可以反映是否有线程正在获得写锁,会检查AQS队列中头部是不是写锁,如果是写锁,那么就不能让线程一直加读锁,这样会导致写锁饿死。

读锁解锁

// 最终会由tryReleaseShared来执行读锁的释放
protected final boolean tryReleaseShared(int unused) {
	//获得当前线程
     Thread current = Thread.currentThread();
     
    ...
      
      //循环直到自己的读计数-1,CAS更新成功
     for (;;) {
         int c = getState();
         int nextc = c - SHARED_UNIT;
         if (compareAndSetState(c, nextc))
             // Releasing the read lock has no effect on readers,
             // but it may allow waiting writers to proceed if
             // both read and write locks are now free.
             return nextc == 0;
     }
 }

写锁加锁

protected final boolean tryAcquire(int acquires) {
      Thread current = Thread.currentThread();
      int c = getState();
      int w = exclusiveCount(c);
      //c!=0说明读锁或者写锁已经被某线程获取
      if (c != 0) {
          // w=O说明已经有线程获取了读锁,w !=O并且当前线程不是写锁拥有者,则返回
false
          if (w == 0 || current != getExclusiveOwnerThread())
              return false;
           //说明当前线程获取了写锁,判断可重入次数
          if (w + exclusiveCount(acquires) > MAX_COUNT)
              throw new Error("Maximum lock count exceeded");
          // 设置可重入次数(1)
          setState(c + acquires);
          return true;
      }
      //第一个写线程获取写锁
      if (writerShouldBlock() ||
          !compareAndSetState(c, c + acquires))
          return false;
      setExclusiveOwnerThread(current);
      return true;
  }

写锁解锁

public final boolean release(int arg) {
	//调用ReentrantReadwriteLock中sync实现的tryRelease方法
	if(tryRelease(arg)) {
		//激活阻塞队列里面的一个线程Node h = head;
	     if (h != null & &h.waitStatus != 0 )
	        unparksuccessor(h);
	        return true;
	    }
	return false;
}

    protected final boolean tryRelease(int releases) {
		// 看是否是写锁拥有者调用的unlock
        if (!isHeldExclusively())
            throw new IllegalMonitorstateException();
		// 获取可重入值,这里没有考虑高16位,因为获取写锁时读锁状态值肯定为0
		int nextc = getstate () - releases;
        boolean free = exclusiveCount(nextc) == 0;
        // 如果写锁可重入值为0则释放锁,否则只是简单地更新状态值
        if (free)
       		 setExclusiveOwnerThread(null);
        setState(nextc);
        return free;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值