Java多线程学习之ReentrantLock
作者:互联网
一、什么是ReentrantLock
ReentrantLock中文译为‘可重入锁’,是‘java.util.concurrent.locks’包下的一个类,实现了‘Lock’接口,在多线程中用来保证线程安全。
使用demo:
public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); new Thread(new Runnable() { @Override public void run() { try { int i = 1; lock.lock(); System.out.println("第" + i + "次获取锁,这个锁是:" + lock); for (i = 2; i < 10; i++) { try { lock.lock(); System.out.println("第" + i + "次获取锁,这个锁是:" + lock); } finally { lock.unlock(); } } } finally { lock.unlock(); } } }).start(); }
二、公平锁和非公平锁
ReentrantLock有三个内部类:Sync、NonfairSync、FairSync。Sync继承了抽象类AbstractQueuedSynchronizer(俗称AQS),NonfairSync、FairSync都继承了Sync,分别采用了非公平锁和公平锁的策略去获取锁。
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
非公平锁:多个线程去获取锁的时候,会直接先去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:这样可能导致队列中间的某个线程一直获取不到锁或者长时间获取不到锁。
根据ReentrantLock的构造函数可知:ReentrantLock默认采用非公平锁:
public ReentrantLock() { sync = new NonfairSync(); }
三、ReentrantLock和AQS
1:ReentrantLock和AQS的关系
ReentrantLock使用demo:
public static void main(String[] args) { ReentrantLock reentrantLock = new ReentrantLock(); try { reentrantLock.lock(); System.out.println("lock"); } finally { reentrantLock.unlock(); } }
点进lock()和unlock()方法,发现实际上是内部类Sync在调用lock()和unlock()方法,而Sync又是继承的AbstractQueuedSynchronizer(AQS),所以ReentrantLock执行的方法实际上是通过AQS来实现的!
public void lock() { sync.lock(); } public void unlock() { sync.release(1); }
2:Sync、NonfairSync、FairSync源码分析
Sync:
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(); //获取AQS的state值 int c = getState(); if (c == 0) { //如果state为0说明资源未被占用即当前线程获取资源成功 //用CAS更新state if (compareAndSetState(0, acquires)) { //设置占用线程为当前线程 setExclusiveOwnerThread(current); return true; } //如果获取失败则把线程改为占用资源的线程 } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // 正常情况下state不可能小于0 throw new Error("Maximum lock count exceeded"); //更新state值 setState(nextc); return true; } return false; } //释放资源 protected final boolean tryRelease(int releases) { //获取当前state int c = getState() - releases; //释放资源的前提是占据了资源,否则不正常,报错! if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //state为0说明当前线程是最后一个获取资源的线程,释放后设置占用资源线程为空 if (c == 0) { free = true; setExclusiveOwnerThread(null); } //更新state setState(c); return free; } //判断当前线程是否为占用资源线程 protected final boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } //创建实例 final ConditionObject newCondition() { return new ConditionObject(); } //获取占用线程 final Thread getOwner() { return getState() == 0 ? null : getExclusiveOwnerThread(); } //获取state值 final int getHoldCount() { return isHeldExclusively() ? getState() : 0; } //判断资源是否被占用(被锁) final boolean isLocked() { return getState() != 0; } //自定义反序列化逻辑 private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // 重置未被锁的状态 } }
NonfairSync:
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; //上锁 final void lock() { //先去尝试获取资源(修改成功则表示获取成功,否则表示获取失败) if (compareAndSetState(0, 1)) //设置当前线程为占用资源线程 setExclusiveOwnerThread(Thread.currentThread()); else //获取失败则加入到等待队列中 acquire(1); } //尝试获取资源,实际是Sync中的“非公平获取”方法的结果 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
FairSync:
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; //上锁 final void lock() { //直接加入到队列中,等待获取资源 acquire(1); } //尝试获取资源 protected final boolean tryAcquire(int acquires) { //获取当前线程 final Thread current = Thread.currentThread(); //获取state int c = getState(); if (c == 0) { //等待队列中前面没有其他线程且能成功修改state值时才把自己设为占据资源线程 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { //设为资源拥有者 setExclusiveOwnerThread(current); return true; } //如果当前线程已占据资源的话,不舍弃资源,仅修改state值 } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } //如果当前线程没有占据资源,且当前state值不为0,则返回false return false; } }
分析:
ReentrantLock的“lock”:实际上是内部‘NonfairSync’或‘FairSync’调用的lock()方法,而两者的区别在于:NonfairSync(非公平锁)会先判断能否获取资源,获取不到再把线程加到等待队列中;FairSync(公平锁)会直接加到等待队列中,等轮到自己的时候才能获取到资源。
ReentrantLock的“unlock”:实际是内部类Sync的release()方法,而在‘release()’中又调用了‘tryRelease(arg)’,因为‘tryRelease’方法是在内部类‘Sync’中实现的,所以并不分“公平锁”和“非公平锁”,即ReentrantLock无论用公平锁还是非公平锁,它的解锁方法都是统一的。
public void unlock() { sync.release(1); } public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
文章参考:https://blog.csdn.net/zhengzhaoyang122/article/details/110847701
标签:Java,lock,ReentrantLock,获取,线程,return,多线程,final 来源: https://www.cnblogs.com/Bernard94/p/16249188.html