Java并发编程-Lock

205 阅读3分钟

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

前言

前面介绍了并发编程同步的手段,synchronized和Lock.Java中的各种锁, 公平锁非公平锁, 可重入锁不可重入锁, 读写锁等等概念很多, 本文主要结合JDK1.8 ReentrantLock源码,谈谈对这些锁的概念上的理解.

1. Lock 框架

image.png

1.1 ReentrantLock

1.1.1 ReentrantLock实现原理

ReentrantLock是基于AQS并发框架实现的并发控制类.ReentrantLock 内部实现3个类,Sync继承AQS, FairSync和NonfairSync都继承于Sync,实现获取锁的各种方法.

基本实现:

  1. 先通过CAS尝试获取锁
  2. 如果此时已有线程占有了锁,那就加入CLH队列并挂起
  3. 当锁释放之后,排在CLH队列队首的线程会被唤醒,然后CAS再次获取锁。

基本使用

image.png 分析lock源码调用的是sync.lock(); image.png

从ReentrantLock构造函数可以看出来,默认调用的是非公平锁. 在往下就是上面说的关键的第一步CAS尝试获取锁

image.png

下面在看else acquire(1), 其实有两个步骤1. tryAcquire (非公平) 2. 加入阻塞队列里面.

image.png

下面是非公平锁acquire的逻辑, 当前线程先判断state状态,状态0,CAS获取锁,获取失败就false, 状态时1,判断当前锁是不是当前线程, 不是的话加到CLH队列里面. image.png

看下和公平锁的区别 image.png

在ReentrantLock中,对于公平和非公平的定义是通过对同步AbstractQueuedSynchronizer的扩展加以实现的,也就是在tryAcquire的实现上做了语义的控制。

1.1.2 公平锁

公平锁,线程会直接进入等待队列里面.

1.1.3 非公平锁

非公平锁,线程会先尝试插队, syncronized是一种非公平锁

1.1.4 可重入锁

所谓重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。synchronized 和   ReentrantLock 都是可重入锁。

可重入锁的意义在于防止死锁。

1.1.5 不可重入锁

所谓不可重入锁,即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞。

把这四种锁放在一起,其实RenntrantLock 源码tryAcquire和tryRelease 方法就实现了这四种锁. 很多博客中也有一些现实生活的例子比如说水井打水的例子很形象容易理解.具体看下参考的博文.

1.2 读写锁

image.png 写锁(独享锁、排他锁),是指该锁一次只能被一个线程所持有。
如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得写锁的线程即能读数据又能修改数据。

读锁(共享锁) 是指该锁可被多个线程所持有。
如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得读锁的线程只能读数据,不能修改数据。

实现原理

AQS中state字段(int类型,32位),此处state上分别描述读锁和写锁的数量于是将state变量“按位切割”切分成了两个部分高16位表示读锁状态(读锁个数) 低16位表示写锁状态(写锁个数)

Image.png

参考文档

AQS的原理浅析
不可不说的Java“锁”事
深入理解AQS实现原理
深入理解读写锁—ReadWriteLock源码分析