显式锁ReentrantLock
学习synchronized关键字的时候我们了解了synchronized锁和它的膨胀过程,虽然Jdk1.5之后对synchronized进行了大量的优化,但是在使用synchronized过程中依然会存在很多问题。因此在Jdk1.5版本以后添加了显示锁Lock。
显式锁Lock
Lock接口是对锁操作的基本定义,它提供了synchronized关键字所具备的全部功能方法,另外我们可以借助Lock创建不同的Condtion对象进行多线程间的通信操作。
Lock接口的定义的方法如下所示:
lock():尝试获取锁。如果锁已经被另一个线程持有,那么该线程会进入阻塞状态,直到获取到锁。
lockInterruptibly():尝试获取锁,进入阻塞的线程是可以被中断的,该方法可以获取中断信号。
tryLock():尝试获取锁,调用该方法获取锁无论是否成功都会立即返回,线程不会进入阻塞状态。
boolean tryLock(long time, TimeUnit unit):该方法与tryLock()方法类似,只是多了获取锁时间的限制,如果在限制的时间内没有获取到锁,则结果返回false。
unlock():释放锁,在持有锁的线程运行结束后,应该确保对锁资源的释放。
newCondition():创建一个与Lock相关的Condition对象。
显示锁Lock有很多实现:ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock。开发中最常用的实现类是ReentrantLock。
ReentrantLock介绍
ReentrantLock是基于AQS同步器构建的锁,只支持独占方式的获取操作,因此它实现了tryAcquire、tryRelease、isHeldExclusively。
ReentrantLock将同步状态state用于保存锁获取操作的次数,并且还维护了一个owner变量用来保存当前所有者线程的标识符。
ReentrantLock的UML图:
ReentrantLock内部类有3个:
-
抽象静态内部类Sync,其继承了AbstractQueuedSynchronizer。
-
静态内部类NonfairSync,其继承了Sync。
-
静态内部类FairSync,其继承了Sync。
ReentrantLock提供了两种锁机制:公平锁和非公平锁。公平锁通过类FairSync提供的方法实现,非公平锁通过NonfairSync提供的方法实现。
接下来我们从源码角度分析这3个内部类的作用。
ReentrantLock内部类分析
Sync是AQS同步器的实现类,其提供了抽象方法lock()由子类实现。其源码解析如下:
abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; //抽象方法,获取锁的方法lock() abstract void lock(); /** * 非公平锁获取方法 */ final boolean nonfairTryAcquire(int acquires) { //获取当前线程 final Thread current = Thread.currentThread(); //获取state同步器状态的值 int c = getState(); //如果state的值为0,说明没有其他线程持有锁 if (c == 0) { //通过CAS的方式修改state的值,多线程环境下state的值可以提前被其他线程抢占 if (compareAndSetState(0, acquires)) { //将线程持有标识设置为当前线程对象 setExclusiveOwnerThread(current); //当前线程获取锁成功返回true return true; } } //如果当前线程是持有锁的线程 else if (current == getExclusiveOwnerThread()) { //将获取的state的值+本次获取锁的个数 int nextc = c + acquires; //如果修改后的数值小于0,则抛出异常 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); //如果不小于0,则修改sate的值 setState(nextc); //当前线程重入成功,返回true return true; } //其他情况均认为当前线程获取锁失败,返回false return false; } //释放锁 protected final boolean tryRelease(int releases) { //获取state状态值,减去需要释放的锁的个数 int c = getState() - releases; //获取当前线程,如果当前线程不是锁的持有者,则抛出异常,此步骤保证只有锁的持有者才可以释放锁 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); //定义free变量初始化为false boolean free = false; //如果c为0,表示锁已经全部释放 if (c == 0) { //修改free为true,表示锁已经全部释放成功 free = true; //设置锁的持有标识为null setExclusiveOwnerThread(null); } //将释放锁之后的值赋值给state setState(c); //如果c为0,则返回true,其他情况锁资源没有完全释放,均返回false return free; } //判断当前线程是否持有某个Lock protected final boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } //创建Condition对象 final ConditionObject newCondition() { return new ConditionObject(); } //获取锁对象的持有线程 final Thread getOwner() { //如果state为0表示没有任何线程持有锁资源返回null,state不为0表示有线程持有锁则返回持有锁的线程 return getState() == 0 ? null : getExclusiveOwnerThread(); } //查询当前线程在某个Lock上的数量,它与monitor计数器的作用是一样的 final int getHoldCount() { //当前线程是锁的持有者则返回state的值,不是则返回0 return isHeldExclusively() ? getState() : 0; } //判断锁对象是否被线程持有 final boolean isLocked() { return getState() != 0; } }
NonfairSync非公平锁内部类,提供非公平的锁竞争方法
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; //实现父类Sync的抽象接口 final void lock() { //通过CAS的方式修改state状态,期望值是0,新值是1 //修改成功,则表示获取锁成功,将当前线程设置为锁持有者 //修改失败,则调用AQS的acqiure方法,将当前线程加入同步队列 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } //AQS的子类实现方法tryAcquire,其调用了父类Sync的非公平锁获取方法nonfairTryAcquire protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
非公平锁加锁流程图:
FairSync公平锁内部类,提供公平的锁竞争方法
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; //实现父类Sync的抽象接口 final void lock() { //调用AQS的acqiure方法,将当前线程加入同步队列 acquire(1); } //AQS的子类实现方法tryAcquire,它没有调用父类Sync的方法,而是自己实现了tryAcquire方法 protected final boolean tryAcquire(int acquires) { //获取当前线程对象 final Thread current = Thread.currentThread(); //获取同步器状态值 int c = getState(); //如果c为0,表示锁资源没有被其他线程持有 if (c == 0) { //判断同步队列中的当前线程节点是head的下一个节点 //如果是则通过CAS的方式修改state的值,修改成功将当前线程设置为锁的持有者 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); //当前线程获取锁成功返回true return true; } } //如果当前线程是锁的持有者,则将状态的值+此次需要获取的锁的个数将其赋值给state else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); //当前线程获取锁成功返回true return true; } //其他情况锁获取失败,返回false return false; } } //AQS方法 public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; //如果头节点和尾节点相等,则返回false,表示同步队列中只有一个节点是当前节点 //如果不相等,那么如果头节点的next不等于null并且next的节点线程是当前节点返回false return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
公平锁加锁流程图:
ReentrantLock用法
我们先看下ReentrantLock的构造函数,ReentrantLock提供了两个构造函数,可以指定锁获取的方式:公平和非公平。
默认情况下为非公平锁。
public class ReentrantLock implements Lock, java.io.Serializable { /** 内部类Sync对象 */ private final Sync sync; /** * 无参构造,默认是非公平锁 */ public ReentrantLock() { sync = new NonfairSync(); } /** * 有参构造,可以指定公平锁和非公共锁 */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } }
可以通过不同的构造方法创建ReentrantLock对象,从而实现不同的加锁和解锁操作。
private final ReentrantLock lock = new ReentrantLock(); public void fun() { lock.lock(); try { //同步代码逻辑 }finally { lock.unlock(); } }
熟悉了ReentrantLock用法之后,我们通过一段代码再熟悉下ReentrantLock的特点。
public class ReentrantLockTest { private final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { ReentrantLockTest test = new ReentrantLockTest(); test.creatThread().start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } //main线程不能释放其他线程加的锁 //test.lock.unlock(); test.lock.lock(); System.out.println("main线程计数器=" + test.lock.getHoldCount()); test.lock.unlock(); } public Thread creatThread() { return new Thread(()->{ lock.lock(); try { System.out.println("计数器=" + lock.getHoldCount()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } //加两次锁,只释放一次,会一直阻塞 // lock.lock(); } finally { lock.unlock(); } }); } }
代码中注释了两行,大家可以去掉注释查看不同的运行效果。
ReentrantLock的用法比较简单,在使用的时候需注意加锁几次就要解锁几次,否则可能出现锁资源无法释放导致一直阻塞的情况。
锁的释放写在finally语句块中保证锁资源一定释放。
只有获取锁的线程才能释放锁资源,没有持有锁的线程释放锁资源抛出IllegalMonitorStateException异常。
ReentrantLock是独占锁,main线程只有在thread线程释放锁资源后才能获取到锁。
ReentrantLock总结
ReentrantLock与synchronized的区别:
-
特点:synchronized是独占可重入锁,是非公平的竞争锁方式。ReentrantLock也是独占可重入锁,但是其可以指定为公平锁,默认是非公平锁。
-
用法:synchronized可以修饰方法和代码块,不需要显示的加锁和解锁。ReentrantLock修饰代码块,在lock()和unlock()方法中间的代码都是同步代码,需要显示的加锁和解锁,将锁的控制权交给了开发人员。
-
性能:基于JVM对关键字的支持,单线程下synchronized关键字性能要优于ReentrantLock,但是多线程环境下ReentrantLock性能优于synchronized。
-
高级特性:获取synchronized锁失败的线程会一直阻塞直到获取到锁,不能中断。ReentrantLock提供了可中断获取锁的方法lockInterruptibly(),而且还提供了获取锁失败不阻塞立即返回的方法tryLock(),如果开发场景中涉及到了高级应用,那就只能选择显示锁Lock了。
公平锁与非公平锁的区别:
-
公平锁:当一个线程尝试获取锁的时候先加入同步队列,如果是下一个需要唤醒的节点则去竞争锁,锁的竞争是先到先得,保证了公平性,但是锁的竞争效率会变低。
-
非公平锁:当一个线程尝试获取锁的时候优先尝试获取锁,如果获取失败再加入同步队列,提高了锁竞争的性能,但是会出现同步队列中的线程一直获取不到锁的现象,称为饥饿现象。
原文链接: https://www.yukx.com/xiaomengbao/article/details/2027.html 优科学习网显式锁ReentrantLock
-
在HTML中,如果你想让一个输入框(input元素)不可编辑,你可以通过设置其readonly属性来实现。示例如下:input type="text" value="此处内容不可编辑" readonly在上述代码中,readonly属性使得用户无法修改输入框中的内容。另外,如果你希望输入框完全不可交
-
ASP.NET教程ASP.NET又称为ASP+,基于.NETFramework的Web开发平台,是微软公司推出的新一代脚本语言。ASP.NET是一个使用HTML、CSS、JavaScript和服务器脚本创建网页和网站的开发框架。ASP.NET支持三种不一样的开发模式:WebPages(Web页面)、
-
C# 判断判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。下面是大多数编程语言中典型的判断结构的通常形式:判断语句C#提供了以下类型的判断语句。点击链接查看每个语句的细节。语句描述if语句一个 if语句 由一个布尔表达式后跟
-
C#循环有的时候,可能需要多次执行同一块代码。通常情况下,语句是顺序执行的:函数中的第一个语句先执行,接着是第二个语句,依此类推。编程语言提供了允许更为复杂的执行路径的多种控制结构。循环语句允许我们多次执行一个语句或语句组,下面是大多数编程语言中循环语句的通常形式:循环类型C#提供了以下几种循环类型
-
C#数组(Array)数组是一个存储相同类型元素的固定大小的顺序集合。数组是用来存储数据的集合,一般认为数组是一个同一类型变量的集合。声明数组变量并不是声明number0、number1、...、number99一个个单独的变量,而是声明一个就像numbers这样的变量,然后使用numbers[0]
-
ASP.NET是一个由微软公司开发的用于构建Web应用程序的框架,它是.NETFramework的一部分。它提供了一种模型-视图-控制器(MVC)架构、Web表单以及最新的ASP.NETCore中的RazorPages等多种开发模式,可以用来创建动态网页和Web服务。以下是一些基础的ASP.NET编