"Interview Tutorial" - прочесывание знаний о блокировке Java

Java
"Interview Tutorial" - прочесывание знаний о блокировке Java

Серия обучающих интервью:

Во-первых, классификация замков

1. Оптимистическая блокировка и пессимистическая блокировка

Оптимистическая блокировка оптимистична в том, что не будет конфликтов, реализованных с помощью cas и номера версии. Пессимистическая блокировка заключается в том, чтобы думать, что будет конфликт, и блокировать операцию.

Пессимистический замок

Пессимистические блокировки всегда предполагают наихудший случай.Каждый раз, когда вы идете за данными, вы думаете, что другие изменят их, поэтому каждый раз, когда вы получаете данные, вы блокируете их, так что другие будут блокировать, пока они не получат блокировку, если вы хочу получить данные..

传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
再比如 Java 里面的同步原语 synchronized 关键字的实现也是悲观锁。

Применимая сцена:

Он больше подходит для сценариев с частыми операциями записи, при большом количестве операций чтения каждое чтение будет заблокировано, что увеличит накладные расходы на большое количество блокировок и снизит пропускную способность системы.

Метод реализации:synchronizedиLock

2. Оптимистичная блокировка

Каждый раз, когда я иду за данными, я думаю, что другие не будут их изменять, поэтому они не будут заблокированы, но когда они будут обновлены, они будут судить, обновляли ли другие данные за этот период, и могут использовать номер версии. и другие механизмы

ABA问题(JDK1.5之后已有解决方案):CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。

循环时间长开销大:CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。

只能保证一个共享变量的原子操作(JDK1.5之后已有解决方案):对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。

Применимая сцена:

Это больше подходит для сценариев с частыми операциями чтения. Если происходит большое количество операций записи, вероятность конфликта данных возрастет. Чтобы обеспечить согласованность данных, прикладной уровень должен постоянно повторно извлекать данные, что увеличит большое количество операций Query снижает пропускную способность системы.

Метод реализации:

1. Используйте идентификатор версии, чтобы определить, согласуются ли прочитанные данные с отправленными данными. После отправки измените идентификатор версии. Если он несовместим, вы можете принять стратегию отказа и повторной попытки.

2. Сравнение и обмен в Java — это CAS.Когда несколько потоков пытаются использовать CAS для одновременного обновления одной и той же переменной, только один из них может обновить значение переменной, в то время как другие потоки терпят неудачу, а отказавший поток не будет быть отстраненным. , но ему сказали, что конкурс провалился и можно попробовать еще раз.

3. В Явеjava.util.concurrent.atomicКласс атомарной переменной в пакете реализован с использованием CAS, реализации оптимистической блокировки.

2. Справедливая блокировка/несправедливая блокировка

Честный замок:

指多个线程按照申请锁的顺序来获取锁。

Несправедливая блокировка:

指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。
有可能,会造成优先级反转或者饥饿现象。

расширять线程饥饿:

一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态
导致无法获取的原因:
线程优先级较低,没办法获取cpu时间
其他线程总是能在它之前持续地对该同步块进行访问。
线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方法),因为其他线程总是被持续地获得唤醒。

Метод реализации:ReenTrantLock(справедливо/несправедливо)

заJava ReentrantLockДругими словами, является ли блокировка справедливой блокировкой, указывается в конструкторе, а по умолчанию используется нечестная блокировка. Преимущество нечестных блокировок в том, что пропускная способность выше, чем у честных блокировок.

заSynchronizedЭто также несправедливый замок. Потому что это не похожеReentrantLockчерез AQS (AbstractQueuedSynchronizer)для реализации планирования потоков, поэтому нет способа сделать его справедливой блокировкой.

3. Повторно входящая блокировка

Если поток получил блокировку, ее можно получить снова, в основном для рекурсии и для предотвращения взаимоблокировки, например, при вызове другого блока метода синхронизации того же объекта блокировки в блоке метода синхронизации.

Метод реализации:synchronized,ReentrantLock

4. Эксклюзивная блокировка/общая блокировка

独享锁是指该锁一次只能被一个线程所持有。
共享锁是指该锁可被多个线程所持有。

Метод реализации: Эксклюзивный замок:ReentrantLockиsynchronizedБлокировка вклада:ReadWriteLock

расширять:

Блокировка мьютекса/чтения-записи является конкретной реализацией вышеизложенного:

互斥锁:在Java中的具体实现就是ReentrantLock,synchronized
读写锁:在Java中的具体实现就是ReadWriteLock

Для Java ReentrantLock это эксклюзивная блокировка. Но для другого класса реализации Lock, ReadWriteLock, его блокировка чтения является общей блокировкой, а его блокировка записи — монопольной блокировкой. Общая блокировка блокировки чтения может гарантировать, что одновременное чтение будет очень эффективным, а процессы чтения и записи, записи и чтения, записи и записи будут взаимоисключающими. Эксклюзивные и общие блокировки также реализуются через AQS, а монопольные или общие блокировки могут быть реализованы с помощью различных методов. Для Synchronized, разумеется, это эксклюзивная блокировка.

5. Замок смещения/легкий замок/тяжелый замок

基于 jdk 1.6 以上

偏向锁Это означает, что в настоящее время его получает только этот поток, и конкуренции нет.В это время установите markword заголовка метода в 0, а затем просто cas каждый раз, когда он приходит, нет необходимости повторно получать блокировку. Это означает, что часть кода синхронизации всегда удерживается потоком доступа, поток автоматически получит блокировку. Снизить стоимость приобретения замков

轻量级锁: На основе смещенной блокировки есть потоки, которые будут конкурировать за нее. В настоящее время она расширяется до облегченной блокировки. Когда несколько потоков получают блокировки, они получаются путем вращения cas вместо блокировки.

重量级锁: после того, как легкая блокировка прокручивается определенное количество раз, она расширяется до тяжелой блокировки, и другие потоки блокируются. Когда поток, получающий блокировку, освобождает блокировку, он пробуждает другие потоки. (Блокирование и пробуждение потока имеют гораздо большее влияние, чем время переключения контекста, включая переключение между режимом пользователя и режимом ядра)

Метод реализации:synchronized

6. Блокировка сегмента

В concurrenthashmap версии 1.7 реализована сегментированная блокировка, представляющая собой массив из 16 сегментов по умолчанию, из которых сегмент наследуется от reentranklock.Каждый поток получает блокировку, а затем оперирует картой, связанной с блокировкой.

Метод реализации:

我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,
它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;
同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,
然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。

但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

Во-вторых, базовая реализация блокировки

1. Синхронизировано

Синхронизированное ключевое слово реализовано парой инструкций байт-кода monitorenter/monitorexit.

Необходимые знания:

对象头:
Hotspot 虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。其中:

Klass Point 是是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

Mark Word 用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键,所以下面将重点阐述 Mark Word 。

Monitor:
每一个 Java 对象都有成为Monitor 的潜质,因为在 Java 的设计中 ,每一个 Java 对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者 Monitor 锁

Структура заголовка объекта:

Структура данных монитора:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //记录个数
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }
参考: https://blog.csdn.net/javazejian/article/details/72828483

В ObjectMonitor есть две очереди,_WaitSetи_EntryList, сохранитьObjectWaiterСписок объектов (каждый поток, ожидающий блокировки, будет инкапсулирован как объект ObjectWaiter),_ownerуказать, чтобы держатьObjectMonitorПоток объекта, когда несколько потоков одновременно обращаются к синхронизированному коду, сначала войдет_EntryListКоллекция, когда поток получает объектmonitorпосле входа_Ownerплощадь и положитьmonitorсерединаownerПеременная устанавливается в текущий поток в мониторе в то же время计数器count加1.

Если поток вызывает метод wait(), текущий удерживаемый монитор будет освобожден, переменная владельца будет восстановлена ​​до нуля, счетчик будет уменьшен на 1, а поток войдет в коллекцию WaitSet и будет ждать пробуждения. Если текущий поток завершает выполнение, он освобождает монитор (блокировка) и сбрасывает значение переменной, чтобы другие потоки могли войти и получить монитор (блокировка).

这里比较复杂,但是建议仔细阅读,便于后续分析的时候理解

1.1, реализация байт-кода

Блок синхронизированного кода:
public class SynchronizedTest {

    public void test2() {
        synchronized(this) {
        }
    }
}

synchronizedКлючевое слово реализует процесс получения и освобождения блокировки на основе двух приведенных выше инструкций:

monitorenterИнструкция вставляется в начало синхронизированного блока кода,

monitorexitИнструкция вставляется в конец блока синхронизированного кода.

Когда поток выполняет команду monitorenter, он попытается получить право собственности на монитор, соответствующий объекту, то есть попытается получить блокировку объекта.

当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor (关于重入性稍后会分析),重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。

Метод синхронизации:
synchronized 方法则会被翻译成普通的方法调用和返回指令如:
invokevirtual、areturn 指令,在 JVM 字节码层面并没有任何特别的指令来实现被synchronized 修饰的方法,
而是在 Class 文件的方法表中将该方法的 access_flags 字段中的 synchronized 标志位置设置为 1,
表示该方法是同步方法,并使用调用该方法的对象或该方法所属的 Class 
在 JVM 的内部对象表示 Klass 作为锁对象
 //省略没必要的字节码
  //==================syncTask方法======================
  public synchronized void syncTask();
    descriptor: ()V
    //方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field i:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field i:I
        10: return
      LineNumberTable:
        line 12: 0
        line 13: 10
}
SourceFile: "SyncMethod.java"

Ссылки на следующие разделы:Синхронизированная реализация анализа исходного кода JVM

1.2. Получение блокировки смещения

1、获取对象头的Mark Word;
2、判断mark是否为可偏向状态,即mark的偏向锁标志位为 1,锁标志位为 01;
3、判断mark中JavaThread的状态:如果为空,则进入步骤(4);如果指向当前线程,
则执行同步代码块;如果指向其它线程,进入步骤(5);
4、通过CAS原子指令设置mark中JavaThread为当前线程ID,
如果执行CAS成功,则执行同步代码块,否则进入步骤(5);
5、如果执行CAS失败,表示当前存在多个线程竞争锁,当达到全局安全点(safepoint),
获得偏向锁的线程被挂起,撤销偏向锁,并升级为轻量级,升级完成后被阻塞在安全点的线程继续执行同步代码块;

В большинстве случаев блокировки не только не имеют многопоточной конкуренции, но и всегда запрашиваются одним и тем же потоком несколько раз, поэтому, чтобы уменьшить стоимость получения блокировок одним и тем же потоком (что включает в себя некоторые операции CAS, которые трудоемко), вводятся смещенные замки. Основная идея предвзятой блокировки заключается в том, что если поток получает блокировку, блокировка переходит в режим смещения, и структура слова метки также становится структурой смещения блокировки.Когда поток снова запрашивает блокировку, нет необходимо выполнять какие-либо операции синхронизации, т.е. процесс получения блокировки экономит много операций, связанных с применением блокировки, тем самым повышая производительность программы. Таким образом, в случае отсутствия конкуренции замков смещенная блокировка имеет хороший эффект оптимизации, ведь очень вероятно, что один и тот же поток применяется для одной и той же блокировки много раз подряд.

Обратите внимание, что JVM предоставляет механизм для отключения предвзятых блокировок.Команда запуска JVM может указывать следующие параметры

-XX:-UseBiasedLocking

Отзыв предвзятых блокировок:

偏向锁的 撤销(revoke) 是一个很特殊的操作, 为了执行撤销操作, 需要等待全局安全点(Safe Point), 
此时间点所有的工作线程都停止了字节码的执行。

偏向锁这个机制很特殊, 别的锁在执行完同步代码块后, 都会有释放锁的操作, 而偏向锁并没有直观意义上的“释放锁”操作。

引入一个概念 epoch, 其本质是一个时间戳 , 代表了偏向锁的有效性

1.3, облегченный замок

В случае нескольких потоков, попеременно выполняющих синхронизированные блоки, старайтесь избегать потребления производительности, вызванного тяжеловесными блокировками, но если несколько потоков одновременно входят в критическую секцию, это приведет к расширению облегченной блокировки и обновлению тяжеловесных блокировок, поэтому облегченные блокировки казалось, не заменит тяжеловесные замки.

1、获取对象的markOop数据mark;
2、判断mark是否为无锁状态:mark的偏向锁标志位为 0,锁标志位为 01;
3、如果mark处于无锁状态,则进入步骤(4),否则执行步骤(6);
4、把mark保存到BasicLock对象的_displaced_header字段;
5、通过CAS尝试将Mark Word更新为指向BasicLock对象的指针,如果更新成功,表示竞争到锁,则执行同步代码,否则执行步骤(6);
6、如果当前mark处于加锁状态,且mark中的ptr指针指向当前线程的栈帧,则执行同步代码,否则说明有多个线程竞争轻量级锁,轻量级锁需要膨胀升级为重量级锁;

1.4, усиленный замок

Тяжеловесная блокировка реализуется монитором внутри объекта. Суть монитора в том, чтобы полагаться на реализацию Mutex Lock базовой операционной системы. Переключение между потоками операционной системы требует переключения из пользовательского режима в режим ядра, а стоимость переключения очень высока.

Процесс расширения блокировки:

1、整个膨胀过程在自旋下完成;
2、mark->has_monitor()方法判断当前是否为重量级锁,即Mark Word的锁标识位为 10,如果当前状态为重量级锁,执行步骤(3),否则执行步骤(4);
3、mark->monitor()方法获取指向ObjectMonitor的指针,并返回,说明膨胀过程已经完成;
4、如果当前锁处于膨胀中,说明该锁正在被其它线程执行膨胀操作,则当前线程就进行自旋等待锁膨胀完成,这里需要注意一点,
虽然是自旋操作,但不会一直占用cpu资源,每隔一段时间会通过os::NakedYield方法放弃cpu资源,或通过park方法挂起;
如果其他线程完成锁的膨胀操作,则退出自旋并返回;
5、如果当前是轻量级锁状态,即锁标识位为 00

Отслеживание соревнований:

1、通过CAS尝试把monitor的_owner字段设置为当前线程;
2、如果设置之前的_owner指向当前线程,说明当前线程再次进入monitor,即重入锁,执行_recursions ++ ,记录重入的次数;
3、如果之前的_owner指向的地址在当前线程中,这种描述有点拗口,换一种说法:之前_owner指向的BasicLock在当前线程栈上,
说明当前线程是第一次进入该monitor,设置_recursions为1,_owner为当前线程,该线程成功获得锁并返回;
4、如果获取锁失败,则等待锁的释放;

Суть в том, чтобы через CAS установить в поле _owner монитора текущий поток, если CAS прошел успешно, значит, поток получил блокировку, выскочил из операции спина и выполнил код синхронизации, иначе продолжает быть приостановленным;

Релиз монитора:

Когда поток, удерживающий блокировку, завершает выполнение блока кода синхронизации, блокировка снимается, давая другим потокам возможность выполнить код синхронизации.В HotSpot блокировка снимается при выходе из монитора, и заблокированный поток уведомляется.

1.5, заблокировать оптимизацию содержимого

Снятие блокировки:

消除锁是虚拟机另外一种锁的优化,这种优化更彻底,
Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),
通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,
通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间

Огрубление блокировки:

将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。

Спинлок:

线程的阻塞和唤醒,需要 CPU 从用户态转为核心态。频繁的阻塞和唤醒对 CPU 来说是一件负担很重的工作,势必会给系统的并发性能带来很大的压力。
同时,我们发现在许多应用上面,对象锁的锁状态只会持续很短一段时间。为了这一段很短的时间,频繁地阻塞和唤醒线程是非常不值得的

适应性自旋锁:
自适应就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定

Модернизация замка:

2. Reetrant Lock

2.1 Блокировка

   //加锁
    void lock();

    //解锁
    void unlock();

    //可中断获取锁,与lock()不同之处在于可响应中断操作,即在获
    //取锁的过程中可中断,注意synchronized在获取锁时是不可中断的
    void lockInterruptibly() throws InterruptedException;

    //尝试非阻塞获取锁,调用该方法后立即返回结果,如果能够获取则返回true,否则返回false
    boolean tryLock();

    //根据传入的时间段获取锁,在指定时间内没有获取锁则返回false,如果在指定时间内当前线程未被中并断获取到锁则返回true
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    //获取等待通知组件,该组件与当前锁绑定,当前线程只有获得了锁
    //才能调用该组件的wait()方法,而调用后,当前线程将释放锁。
    Condition newCondition();

В Java 1.5 официальный параллельный пакет(J.U.C)Интерфейс Lock добавлен к интерфейсу, который предоставляет метод lock() и метод unLock() для поддержки явных операций блокировки и снятия блокировки.

Преимущества замков Lock:

可以使锁更公平。
可以使线程在等待锁的时候响应中断。
可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间。
可以在不同的范围,以不同的顺序获取和释放锁。

2.2, AQS (AbstractQueuedSynchronizer)

AQS расшифровывается как синхронизатор очереди. Это базовая структура для создания блокировок или других компонентов синхронизации (таких как ReentrantLock, ReentrantReadWriteLock, Semaphore и т. д.), и автор пакета параллелизма J.U.C (Дуг Ли) ожидает, что он станет основой для большинства потребностей синхронизации.

структура данных:

    //同步队列头节点
    private transient volatile Node head;

    //同步队列尾节点
    private transient volatile Node tail;

    //同步状态
    private volatile int state;

AQS использует состояние переменной-члена типа int для представления состояния синхронизации:

  • когдаstate > 0, указывая на то, что блокировка была получена.
  • когдаstate = 0когда замок снят.

Узел формирует очередь синхронизации FIFO для завершения постановки в очередь блокировок захвата потоков.

  • Если текущему потоку не удается получить состояние синхронизации (блокировку), AQS создаст текущий поток, состояние ожидания и другую информацию в узле (узле) и добавит его в очередь синхронизации, блокируя текущий поток.
  • Когда состояние синхронизации освобождается, поток в узле будет разбужен, чтобы снова попытаться получить состояние синхронизации.

Ссылаться на:Углубленный анализ одновременных (исключительных) блокировок на основе AQS, реентерабельных блокировок (ReetrantLock) и принципа их реализации по условию.

2.3. Синхронизация

Sync: Абстрактный класс, являющийся внутренним классом ReentrantLock, наследуется от AbstractQueuedSynchronizer, реализует операцию снятия блокировки (метод tryRelease()) и предоставляет абстрактный метод блокировки, реализуемый его подклассами.

NonfairSync: внутренний класс ReentrantLock, унаследованный от Sync, класса реализации несправедливой блокировки.

FairSync: внутренний класс ReentrantLock, унаследованный от Sync, класса реализации справедливой блокировки.

Конкретная диаграмма взаимосвязи AQS, Sync и ReentrantLock:

2.4, Принцип реализации ReentrantLock

Конструктор:

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

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

ReentrantLock предоставляет две реализации: честная блокировка/несправедливая блокировка.Инициализация через конструкторsyncДля определения текущего типа блокировки.

2.4.1, Недобросовестная блокировка (NonfairSync)
    final void lock() {
    //cas 获取锁
        if (compareAndSetState(0, 1))
        //如果成功设置当前线程Id
            setExclusiveOwnerThread(Thread.currentThread());
        else
        //否则再次请求同步状态
            acquire(1);
    }

Сначала выполните операцию CAS для состояния синхронизации, попробуйте установить состояние состояния с 0 на 1, Если он возвращает true, это означает, что состояние синхронизации успешно получено, то есть текущий поток получает блокировку и может оперировать критическими ресурсами.Если он возвращает false, это означает, что существующий поток удерживает состояние синхронизации (его значение равно 1). ) Не удалось получить блокировку, обратите внимание, что существует параллельный сценарий, то есть может быть несколько потоков, одновременно устанавливающих переменную состояния, поэтому именно операция CAS обеспечивает атомарность операции переменной состояния. После возврата false выполнитьacquire(1)метод

#acquire(int arg)метод, метод шаблона, предоставленный для AQS. Этот метод получает исключительно состояние синхронизации, но этот метод не чувствителен к прерываниям. Другими словами, поскольку потоку не удается получить состояние синхронизации и он добавляется в очередь синхронизации CLH, при последующем прерывании потока поток не будет удален из очереди синхронизации CLH.

acquireКод:

   public final void acquire(int arg) {
   //尝试获取同步状态
       if (!tryAcquire(arg) &&
           //自旋直到获得同步状态成功,添加节点到队列    
           acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
           selfInterrupt();
   }

1,tryAcquireпопробуй получить статус синхронизации

    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=1 && 线程为当前线程 重入锁的逻辑
            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;
        }

2,acquireQueuedПрисоединяйтесь к очереди, вращайтесь, чтобы получить замок

private Node addWaiter(Node mode) {
   //将请求同步状态失败的线程封装成结点
   Node node = new Node(Thread.currentThread(), mode);

   Node pred = tail;
   //如果是第一个结点加入肯定为空,跳过。
   //如果非第一个结点则直接执行CAS入队操作,尝试在尾部快速添加
   if (pred != null) {
       node.prev = pred;
       //使用CAS执行尾部结点替换,尝试在尾部快速添加
       if (compareAndSetTail(pred, node)) {
           pred.next = node;
           return node;
       }
   }
   //如果第一次加入或者CAS操作没有成功执行enq入队操作
   enq(node);
   return node;
}

   final boolean acquireQueued(final Node node, int arg) {
       boolean failed = true;
       try {
           boolean interrupted = false;
           for (;;) {
           //获取前驱节点
               final Node p = node.predecessor();
               //如果前驱节点试头节点, 尝试获取同步状态
               if (p == head && tryAcquire(arg)) {
                   setHead(node);
                   p.next = null; // help GC
                   failed = false;
                   return interrupted;
               }
               // 获取失败,线程等待
               if (shouldParkAfterFailedAcquire(p, node) &&
                   parkAndCheckInterrupt())
                   interrupted = true;
           }
       } finally {
           if (failed)
               cancelAcquire(node);
       }
   }

блок-схема:

2.4.2, справедливая блокировка (FairSync)

В отличие от несправедливых блокировок, при получении блокировок порядок получения честных блокировок полностью соответствует правилу FIFO во времени, то есть поток, который запрашивает первым, должен получить блокировку первым, а следующий за ним поток должен быть поставлен в очередь. реализация метода nonfairTryAcquire(int Acquires) метода nonfairTryAcquire(int Acquires), который мы проанализировали ранее для недобросовестной блокировки, ниже приведена реализация метода tryAcquire() для честной блокировки

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

2.4.3. Разблокировать

//ReentrantLock类的unlock
public void unlock() {
    sync.release(1);
}

//AQS类的release()方法
public final boolean release(int arg) {
    //尝试释放锁
    if (tryRelease(arg)) {

        Node h = head;
        if (h != null && h.waitStatus != 0)
            //唤醒后继结点的线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

//ReentrantLock类中的内部类Sync实现的tryRelease(int releases) 
protected final boolean tryRelease(int releases) {

      int c = getState() - releases;
      if (Thread.currentThread() != getExclusiveOwnerThread())
          throw new IllegalMonitorStateException();
      boolean free = false;
      //判断状态是否为0,如果是则说明已释放同步状态
      if (c == 0) {
          free = true;
          //设置Owner为null
          setExclusiveOwnerThread(null);
      }
      //设置更新同步状态
      setState(c);
      return free;
  }

3. РеентерабельныйReadWriteLock

Конструктор:

Lock readLock();

Lock writeLock();

/** 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock */
public ReentrantReadWriteLock() {
    this(false);
}

/** 使用给定的公平策略创建一个新的 ReentrantReadWriteLock */
public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

java.util.concurrent.locks.ReentrantReadWriteLock,выполнитьReadWriteLockИнтерфейс, реентерабельный класс реализации блокировки чтения-записи. Внутри него поддерживается пара связанных блокировок, одна для операций только для чтения, а другая для операций записи. пока нетWriterпотоки, блокировки чтения могут удерживаться несколькимиReaderпотоки поддерживаются одновременно. Другими словами, блокировки записи являются эксклюзивными, а блокировки чтения — общими.

В ReentrantLock состояние типа int для Sync (фактически AQS) используется для представления состояния синхронизации, которое представляет количество повторных захватов блокировки потоком. Однако блокировка чтения-записи ReentrantReadWriteLock внутренне поддерживает пару блокировок чтения-записи. его на две части: высокие 16 для чтения, нижние 16 для записи.

Как после разделения блокировка чтения-записи быстро определяет состояние блокировок чтения и записи? побитовыми операциями. Если текущее состояние синхронизации S, то:

  • Статус записи, равный S&0x0000FFFF (все старшие 16 бит стираются)
  • Статус чтения, равный S >>> 16 (0 без знака, сдвиг вправо на 16 бит).

1. блокировка чтения

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    
    protected final int tryAcquireShared(int unused) {
    //当前线程
    Thread current = Thread.currentThread();
    int c = getState();
    //exclusiveCount(c)计算写锁
    //如果存在写锁,且锁的持有者不是当前线程,直接返回-1
    //存在锁降级问题,后续阐述
    if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
        return -1;
    //读锁
    int r = sharedCount(c);

    /*
     * readerShouldBlock():读锁是否需要等待(公平锁原则)
     * r < MAX_COUNT:持有线程小于最大数(65535)
     * compareAndSetState(c, c + SHARED_UNIT):设置读取锁状态
     */
    if (!readerShouldBlock() &&
            r < MAX_COUNT &&
            compareAndSetState(c, c + SHARED_UNIT)) { //修改高16位的状态,所以要加上2^16
        /*
         * holdCount部分后面讲解
         */
        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;
    }
    return fullTryAcquireShared(current);
}
    
    

4. Каковы сходства и различия между синхронизированным и ReentrantLock?

Та же точка

都实现了多线程同步和内存可见性语义。
都是可重入锁。

разница

同步实现机制不同
synchronized 通过 Java 对象头锁标记和 Monitor 对象实现同步。
ReentrantLock 通过CAS、AQS(AbstractQueuedSynchronizer)和 LockSupport(用于阻塞和解除阻塞)实现同步。


可见性实现机制不同
synchronized 依赖 JVM 内存模型保证包含共享变量的多线程内存可见性。
ReentrantLock 通过 ASQ 的 volatile state 保证包含共享变量的多线程内存可见性。

使用方式不同
synchronized 可以修饰实例方法(锁住实例对象)、静态方法(锁住类对象)、代码块(显示指定锁对象)。
ReentrantLock 显示调用 tryLock 和 lock 方法,需要在 finally 块中释放锁。

功能丰富程度不同
synchronized 不可设置等待时间、不可被中断(interrupted)。
ReentrantLock 提供有限时间等候锁(设置过期时间)、可中断锁(lockInterruptibly)、condition(提供 await、condition(提供 await、signal 等方法)等丰富功能

锁类型不同
synchronized 只支持非公平锁。
ReentrantLock 提供公平锁和非公平锁实现。当然,在大部分情况下,非公平锁是高效的选择。