JUC探险-7、ReentrantLock

18 篇文章 0 订阅

一、:ReentrantLock简介

  在了解了Lock & AQS的基本实现内容之后,我们就可以来看看经典的实现类ReentrantLock了。在AQS之中,类似tryAcquire()等方法是需要后续类去实现内容的,在这里我们就可以看到具体的实现细节。

  ReentrantLock是可重入互斥锁,实现了Lock接口,也是在实际编程中使用频率很高的一个锁。
  具有可重入性,表示能够对共享资源重复加锁,即当前线程再次获取该锁不会被阻塞。具有互斥性,表示在某一时间点只能被同一线程所拥有。
  在java关键字中,synchronized隐式支持重入性,synchronized通过获取自增、释放自减的方式实现重入。
  与此同时,ReentrantLock还支持公平锁非公平锁两种方式。

  可见,学习ReentrantLock,主要看两个方面的问题:1、重入性的实现原理;2、公平锁和非公平锁。

  ①ReentrantLock初窥

public class ReentrantLock implements Lock, java.io.Serializable {
	private final Sync sync;
	abstract static class Sync extends AbstractQueuedSynchronizer {
	}
	
	static final class NonfairSync extends Sync {
		final void lock() {
		}
		
		protected final boolean tryAcquire(int acquires) {
		}
	}
	static final class FairSync extends Sync {
		final void lock() {
		}
		
		protected final boolean tryAcquire(int acquires) {
		}
	}

	// 获取锁
	public void lock() {
    }
    public void lockInterruptibly() throws InterruptedException {
    }
    
    // 仅在调用时锁未被另一个线程持有的情况下,才获取该锁
    public boolean tryLock() {
    }
    // 如果锁在给定等待时间内没有被另一个线程持有,且当前线程未被中断,则获取该锁
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
    }
    
    // 试图释放锁
    public void unlock() {
    }
    
    // 返回用来与此 Lock 实例一起使用的 Condition 实例
    public Condition newCondition() {
    }
    
    // 查询当前线程持有此锁的次数
    public int getHoldCount() {
    }
    ...
}

    进入很多方法,不难看到,基本都会调用sync的方法最终实现功能。而sync的最终实现会落在公平或者非公平同步代码中。

  ②ReentrantLock重入性的实现原理

    要想支持重入性,需要解决两个问题:
      ●在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功。
      ●由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。

    以非公平锁为例,来仔细看看ReentrantLock重入性的实现。

    1、nonfairTryAcquire()方法

      获取锁时,NonfairSync在重写tryAcquire()方法时,内部直接调用了nonfairTryAcquire()方法,因此直接来到这个方法:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 1.如果该锁未被任何线程占有,该锁能被当前线程获取
   	if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
	// 2.若锁被占有,检查占有线程是否是当前线程
    else if (current == getExclusiveOwnerThread()) {
		// 3.再次获取,计数加1
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

      为了支持重入性,在第2步增加了处理逻辑:如果该锁已经被线程所占有了,会继续检查占有线程是否为当前线程,如果是的话,同步状态加1返回true,表示可以再次获取成功。每次重新获取都会对同步状态进行加1操作。

    2、tryRelease()方法

      对于释放锁,这个方法直接在抽象类Sync中重写了:

protected final boolean tryRelease(int releases) {
    // 1.同步状态减1
    int c = getState() - releases;
    // 如果当前线程不是持有锁线程时抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
   		// 2.只有当同步状态为0时,锁成功被释放,返回true
        free = true;
        setExclusiveOwnerThread(null);
    }
	// 3.锁未被完全释放,设置新次数,返回false
    setState(c);
    return free;
}

      这里可以看到,重入锁的释放必须得等到同步状态为0时锁才算成功释放,否则锁仍未释放。如果锁被获取了n次,释放了小于n次,该锁未完全释放返回false,只有被释放n次才算成功释放,返回true。

  ③公平锁与非公平锁

    如果一个锁是公平的,那么锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO。如果可以竞争获取锁,就是非公平的。
    ReentrantLock提供构造非公平锁和公平锁的两种构造方法:(默认非公平锁)

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

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

    非公平锁的获取方法nonfairTryAcquire()在上面已经介绍过,下面来看下公平锁的获取方法tryAcquire()。

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

      代码的逻辑与nonfairTryAcquire()基本一致,唯一的不同在于增加了hasQueuedPredecessors()的逻辑判断,见名知意该方法用来判断当前节点在同步队列中是否有前驱节点的,如果有前驱节点说明有线程比当前线程更早的请求资源,根据公平性,当前线程请求资源失败。如果当前节点没有前驱节点的话,才有做后面的逻辑判断的必要性。
      公平锁每次都是从同步队列中的第一个节点获取到锁,而非公平性锁则不一定,有可能刚释放锁的线程能再次获取到锁。

    对比公平锁与非公平锁:
      ●公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,极端情况下有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。
      ●公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。


二、:ReentrantLock VS synchronized

  之前提到Lock提供了比synchronized更加灵活和强大的锁机制,那么就ReentrantLock而言,和synchronized有什么相似、不同呢?
    ●与synchronized相比,ReentrantLock提供了更多、更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票等。
    ●ReentrantLock还提供了条件Condition,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock更加适合。
    ●ReentrantLock提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而synchronized则一旦进入锁请求要么成功要么阻塞,所以相比synchronized而言,ReentrantLock会不容易产生死锁一些。
    ●ReentrantLock支持更加灵活的同步代码块,但是使用synchronized时,只能在同一个synchronized块结构中获取和释放。注意:ReentrantLock的锁释放一定要在finally中处理,否则可能会产生严重的后果。
    ●ReentrantLock支持中断处理,且性能较synchronized会好一些。

系列文章传送门:

JUC探险-1、初识概貌
JUC探险-2、synchronized
JUC探险-3、volatile
JUC探险-4、final
JUC探险-5、原子类
JUC探险-6、Lock & AQS
JUC探险-7、ReentrantLock
JUC探险-8、ReentrantReadWriteLock
JUC探险-9、Condition
JUC探险-10、常见工具、数据结构
JUC探险-11、ConcurrentHashMap
JUC探险-12、CopyOnWriteArrayList
JUC探险-13、ConcurrentLinkedQueue
JUC探险-14、ConcurrentSkipListMap
JUC探险-15、BlockingQueue
JUC探险-16、ThreadLocal
JUC探险-17、线程池

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值