The Art Notes of Java Concurrent Programming - Locks in Threads

J___code 2022-08-06 15:01:45 阅读数:823

artnotesjavaconcurrentprogramming

1.Lock接口

  • You need to explicitly acquire and release locks when using them
  • The convenience of implicitly acquiring and releasing locks is missing,But it has lock acquisition and release可操作性、可中断的获取锁以
    and time out to acquire the lock等多种synchronized关键字所不具备的同步特性
  • LockThe implementation of the interface is basically throughAggregates a subclass of SynchronizerTo complete the thread access control(可以参考A custom sync component in a queue synchronizer——TwinsLock部分)

在这里插入图片描述

不要将获取锁的过程写在try块中(If an exception occurs while acquiring the custom lock,When an exception is thrown, the lock will be released for no reason)


2.队列同步器

  • 用来构建锁或者其他同步组件的基础框架

  • 使用int成员变量表示同步状态(如ReentrantLockIn the implementation of the corresponding custom synchronizer of the lock,同步状态表示锁被一个线程重复获取的次数),通过内置的FIFO队列来完成资源获取线程的排队工作

  • Both can support exclusive access to the synchronization state,也可以支持共享式地获取同步状态

  • 锁是面向使用者的,它定义了使用者与锁交互的接口,隐藏了实现细节;同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作

  • 同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态

  • 同步器提供的模板方法基本上分为3类:

    • 独占式获取与释放同步状态
    • 共享式获取与释放同步状态
    • 查询同步队列中的等待线程情况

2.1 队列同步器的接口与示例

class Mutex implements Lock {

// 静态内部类,自定义同步器
private static class Sync extends AbstractQueuedSynchronizer {

// 是否处于占用状态
protected boolean isHeldExclusively() {

return getState() == 1;
}
}
... Implementations of other methods of the synchronizer
// 仅需要将操作代理到Sync上即可
private final Sync sync = new Sync();
// 下面方法是Mutex的方法,But the actual call is the static inner classSync的方法
public boolean isLocked() {
 return sync.isHeldExclusively(); }
...其他调用了Sync方法的方法
}

2.2 队列同步器的实现分析

2.2.1 .同步队列
  • 同步队列中的节点用来保存获取同步状态失败的线程引用、等待状态以及前驱和后继节点

  • 同步器拥有首节点和尾节点,没有成功获取同步状态的线程将会成为节点加入该队列的尾部

  • Synchronizer provides one基于CAS的设置尾节点的方法:compareAndSetTail(Node expect,Node update),它需要传递当前线程“认为”的尾节点和当前节点

  • Set the first node to be通过获取同步状态成功的线程来完成,由于只有一个线程能够成功获取到同步状态,因此The method of setting the head node does not need to be usedCAS来保证

2.2.2 独占式同步状态获取与释放

同步状态获取:

public final void acquire(int arg) {

// tryAcquire:保证线程安全的获取同步状态,如果同步状态获取失败,则构造同步节点
// addWaiter:将Node.EXCLUSIVE节点(The node guarantees that only one thread can successfully obtain the synchronization state at the same time)加入到同步队列的尾部
// acquireQueued:使得该节点以“死循环”的方式获取同步状态(Threads in nodes that were not successfully acquired are blocked,Nodes are still spinning.Blocking threads need to go through the predecessor node(Needs to be the head node)is dequeued or interrupted to wake up)
// 注意:Nodes basically do not communicate with each other during the cyclic check process,而是简单地判断自己的前驱是否为头节点
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

在这里插入图片描述

当前线程获取同步状态并执行了相应逻辑之后,就需要释放同步状态,使得后续节点能够继续获取同步状态:

public final boolean release(int arg) {

if (tryRelease(arg)) {

Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒处于等待状态的线程
return true;
}
return false;
}

综上所述:

  • 在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋
  • 移出队列(或停止自旋)的条件是前驱节点为头节点且成功获取了同步状态
  • 在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后唤醒头节点的后继节点
2.2.3 共享式同步状态获取与释放

共享式获取与独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态

  • 在共享式获取的自旋过程中,成功获取到同步状态并退出自旋的条件就是tryAcquireShared(int arg)方法返回值大于等于0

  • 在释放同步状态时,The main difference from exclusive is thattryReleaseShared(int arg)方法必须确保同步状态线程安全释放(一般是通过循环和CAS来保证的,因为释放同步状态的操作会同时来自多个线程)

2.2.4 独占式超时获取同步状态
  • 调用同步器的doAcquireNanos(int arg,long nanosTimeout)方法可以超时获取同步状
    态(即在指定的时间段内获取同步状态,如果获取到同步状态则返回true,否则,返回false)
  • The main difference between the exclusive time-out acquisition of the synchronization state and the exclusive acquisition of the synchronization state lies in the processing logic when the synchronization state is not acquired:
    • acquire(int args)在未获取到同步状态时,将会使当前线程一直处于等待状态
    • doAcquireNanos(int arg,long nanosTimeout)会使当前线程等待nanosTimeout纳秒,如果当前线程在nanosTimeout纳秒内没有获取到同步状态,将会从等待逻辑中自动返回

在这里插入图片描述


3.重入锁

  • 重入锁ReentrantLockis a lock that supports reentry,该锁支持一个线程对资源的重复加锁.除此之外,the lock also支持获取锁时的公平和非公平性选择

  • synchronized关键字隐式的支持重进入

  • 如果在绝对时间上,先对锁进行获取的请求一定先被满足,则该锁是公平的(FIFO),反之是不公平的

3.1 实现重进入

Achieving reentry requires addressing the following two issues:

  • 线程再次获取锁:锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取
  • 锁的最终释放:Locks are incremented for acquisitions(计数表示当前锁被重复获取的次数);锁被释放时,计数自减,当计数等于0时表示锁已经成功释放

3.2 公平与非公平获取锁的区别

  • 公平性与否是针对获取锁而言的,如果一个锁是公平的,Then the order in which the locks are acquired should conform to the absolute time order of the request(即FIFO)
  • ReentrantLockThe implementation of acquiring the two locks is different in :
// 非公平锁
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;
}
// 如果是获取锁的线程再次请求
} else if (current == getExclusiveOwnerThread()) {

int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc); // Increment the synchronization state value and returntrue
return true;
}
return false;
}
// 公平锁
protected final boolean tryAcquire(int acquires) {

final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {

// *Compared to unfair lockshasQueuedPredecessors()方法,即加入了同步队列中当前节点是否有前驱节点的判断,如果该方法返回true,则表示有线程比当前线程更早地请求获取锁
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;
}
  • 公平性锁保证了锁的获取按照FIFO原则,而代价是Do a lot of thread switching;Unfairness locks thoughmay cause threading“饥饿”,但极少的线程切换,保证了其更大的吞吐量

4.读写锁

  • Read-write locks are available at the same timeMultiple read threads are allowed access,但是在写线程访问时,所有的读线程和其他写线程均被阻塞

  • To implement updates after a write operation to a shared cache is visible to subsequent read operations:

    • There are no read-write locks:When the write operation starts, all read operations that are later than the write operation enter the wait state.Notify when the write operation is complete,The pending read operation begins to execute(等待通知机制)
    • 使用读写锁:Acquire a read lock on a read operation,写操作时获取写锁即可.当写锁被获取到时,后续(非当前写操作线程)的读写操作都会被阻塞,写锁释放之后,所有操作继续执行
  • 读写锁的性能都会比排它锁好(In most scenarios there are more reads than writes),It can provide better concurrency and throughput than exclusive locks

  • JUCThe implementation that provides read-write locks in isReentrantReadWriteLock,The lock characteristics are as follows:

在这里插入图片描述

4.1 读写锁的实现分析

4.1.1 读写状态的设计
  • 读写锁同样依赖自定义同步器来实现同步功能,而读写状态就是其同步器的同步状态(ReentrantLockThe synchronization state of the custom synchronizer in , indicates the number of times the lock has been repeatedly acquired by a thread)

  • The read-write lock will represent an integer variable of synchronization stateBitwise segmentation为两个部分:

在这里插入图片描述

4.1.2 写锁的获取与释放
  • 写锁的获取:
    • 如果当前线程已经获取了写锁,则增加写状态;如果当前线程在获取写锁时,读锁已经被获取(读状态不为0)或者该线程不是已经获取写锁的线程,则当前线程进入等待状态
    • 存在读锁,则写锁不能被获取:如果允许读锁在已被获取的情况下对写锁的获取,Then other running reading threads cannot perceive the operation of the current writing thread
protected final boolean tryAcquire(int acquires) {

Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
// c!=0表示32Bit integer variables do not0
if (c != 0) {

// w==0Indicates that the write status is 0,则读状态r一定不为0(因为c=w+r)
// 存在读锁或者当前获取线程不是已经获取写锁的线程
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
setState(c + acquires);
return true;
}
if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) {

return false;
}
setExclusiveOwnerThread(current);
return true;
}
  • 写锁的释放:每次释放均减少写状态,当写状态为0时表示写锁已被释放,从而等待的读写线程能够继续访问读写锁
4.1.3 读锁的获取与释放
  • 读锁的获取:
    • 如果当前线程已经获取了读锁,则增加读状态;如果当前线程在获取读锁时,写锁已被其他线程获取,则进入等待状态
    • 如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态;如果当前线程获取了写锁或者写锁未被获取,则当前线程增加读状态,成功获取读锁
protected final int tryAcquireShared(int unused) {

for (;;) {

int c = getState();
int nextc = c + (1 << 16); // 增加读状态(需要向左移动16位)
if (nextc < c)
throw new Error("Maximum lock count exceeded");
if (exclusiveCount(c) != 0 && owner != Thread.currentThread())
return -1;
// CASGuarantees thread safety when increasing read state
if (compareAndSetState(c, nextc))
return 1;
}
}
  • 读锁的释放:读锁的每次释放均减少读状态,减少的值是(1<<16)
4.1.4 锁降级
  • 锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程

注意:The current thread has a write lock,然后释放,再获取读锁,This cannot be a lock downgrade

  • RentrantReadWriteLock不支持锁升级(把持读锁、获取写锁,最后释放读锁的过程),目的也是保证数据可见性(如果读锁已被多个线程获取,其中任意线程成功获取了写锁并更新了数据,则其更新对其他获取到读锁的线程是不可见的)

copyright:author[J___code],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/218/202208061454273072.html