JUC(1)---ReentrantLock和AQS

Synchronizedjvm内置的锁;而java.util.concurrent包下面的Lock锁是大佬(Doug Lea)用java代码实现的显示锁。

Juc包下面锁相比jvm内置的锁更加灵活。围绕着AQS(AbstractQueuedSynchronizer)实现了一系列性质的锁,比如共享/独占,公平/非公平,重入,阻塞等待。

 

ReentrantLock为例,它有一个字段是sync,是其内部类SyncSync类继承了AbstractQueuedSynchronizer,同时它还有两个子类一个NonfairSync(非公平),一个是FairSync(公平的),显然这两个就是实现分别实现了公平锁和非公平锁。

再看AbstractQueuedSynchronizer的父类AbstractOwnableSynchronizer中有一个字段是exclusiveOwnerThread,从这个字段注释可以看出来(The current owner of exclusive mode synchronization.)这个是记录独占线程的。

另外这AbstractQueuedSynchronizer类中还有一个内部类NodeNode中有一系列的标记,还有一个Thread标记当前的线程。借此来实现锁相关队列。比如同步队列借助Nodeprevnext字段来实现的双向链表。条件队列是借助nextWaiter来实现的单向链表。

同时有一个标记waitStatus来记录当前节点的状态(CANCELLEDSIGNALCONDITIONPROPAGATE)。比方说没抢到锁的就将这个线程新建一个Node扔到队列里面去等着。简单的提了下队列,再说下公平和非公平,公平就是不论怎么样都乖乖的去队列排队等锁;非公平就是上来先去抢锁,抢到就先执行了,没抢到再去队列排队。

 

 

 以上这些就是AQS比较重要的点,整个加锁实现的逻辑都是围绕这些东西来玩的。

上面简单接单介绍AQS中的几个比较关键的点。下面通过代码debug来看下这些东西

首先我们看下单独运行一个线程的时候 关注ReentrantLock这个类中的sync字段的属性变化。我们会看到一个线程的时候同步器(sync)的head,tail字段(这两个就是上面说到Node类)是null,exclusiveOwnerThread是我们当前的这个这个线程,state字段第一次加锁的时候1,第二次加锁会+1,释放一次锁会-1。当state减到0的时候说明当前线程已经完全释放了锁,以此来实现锁重入的的功能。

代码片段:

 

public class SyncTest {
    
    private Lock lock = new ReentrantLock(true);

    public static void main(String[] args) {
        SyncTest syncTest = new SyncTest();
        new Thread(()->syncTest.testLock(), "线程1").start();
    }

    public void testLock() {
        lock.lock();
        System.out.println("线程:--->" + Thread.currentThread().getName() + "第一次加锁");
        lock.lock();
        System.out.println("线程:--->" + Thread.currentThread().getName() + "第二次加锁");
        lock.unlock();
        System.out.println("线程:--->" + Thread.currentThread().getName() + "释放第一个锁");
        lock.unlock();
        System.out.println("线程:--->" + Thread.currentThread().getName() + "释放第一个锁");
    }

}

 

 

三个线程执行:

会看见head 和tail都有值,并且可以看见剩下的两个线程在队列里面,(注意这里head其实只是一个空的头结点,真的头结点是当前执行的线程。这里面head结点主要是为了方便操作声明了)。

 

 

以ReentrantLock中的公平为例:

1.拿到锁的线程,如果加锁标记state是0,那么就通过CAS操作将state改成1,并且将独占线程设置为当前线程,否则判断当前线程是否是独占线程,如果是则重入

 

 2.如果都不是 就放入队列中去排队

 

 

 

 

3.释放锁的时候,当将state修改成0的时候tryRelease方法返回的才是true才会去唤醒后续的结点

 

 

 

 

juc下面的锁基本就是借助AQS(AbstractQueuedSynchronizer队列同步器的各种实现)和Unsafe这个类调用系统层面的CAS操作保证属性的修改,以及线程阻塞和唤醒操作park和unpark,其次就是很多属性都是使用了volatile来保证一个线程修改其他线程对其状态的更改立马可见。

 

posted @ 2020-05-12 21:44  白露非霜  阅读(270)  评论(0编辑  收藏  举报
访问量