VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > Python基础教程 >
  • 简单看看ReentrantLock

 前面我们分析了AQS的基本原理,然后也试着基于AQS实现了一个可重入的锁了,现在我们再来看看官方的ReentrantLock锁,这个锁是可重入的独占锁,也就是说同时只有一个线程可以获取该锁,而且这个线程还能继续尝试获取锁;

 

一.简单的使用

  我们先根据ReentrantLock来简单实现一个线程安全的List,然后再分析常用的方法;

复制代码
package com.example.demo.study;

import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;

public class Study0204 {
    //线程不安全的List
    private ArrayList<String> list = new ArrayList<String>();
    //独占锁,默认是非公平锁,传入true可以是公平锁
    private volatile ReentrantLock lock = new ReentrantLock();
    
    //往集合中添加元素
    public void add(String str) {
        lock.lock();
        try {
            list.add(str);
        } finally {
            lock.unlock();
        }
    }
    //删除集合中的元素
    public void remove(String str) {
        lock.lock();
        try {
            list.remove(str);
        } finally {
            lock.unlock();
        }
    }
    //根据索引获取集合中某个元素
    public String get(int index) {
        lock.lock();
        try {
            return list.get(index);
        } finally {
            lock.unlock();
        }
    }
}
复制代码

  其实就是在每一步都会进行一个加锁的过程,用法和synchronized关键字一样,但是要注意一定要在finally中释放锁,而且不知道大家有没有发现一个问题,在get方法中,需要用锁吗?为什么多线程去读数据也要加锁呢,又没有改变集合中的数据,这也是ReentrantLock这个锁的一个缺陷,使用于写多读少的情况;

  所以后面我们会说一下ReentrantReadWriteLock这个锁,这个锁应用于读多写少的情景,可以把读锁和写锁分开使用,如果是读数据可以多个线程都可以获取读锁,后面会说到的

 

二.看看ReentrantLock锁结构

  这个锁其实也很简单,下图所示:实现Lock接口,还有一个内部工具类Sync继承自AQS,然后还有两个类NonfairSync和FairSync继承Sync实现自己的方法,看名字就知道这两个类其实就是非公平锁和公平锁的实现策略;

 

  首先我们看看ReentrantLock的lock方法,发现就是调用sync的lock方法,所以下一步我们看看这个sync对象是怎么构建出来的;

  

  看看构造器,可以知道根据传入的参数是true和false构建sync对象是公平策略还是非公平策略;顺便一说,在这里AQS中的state表示的是锁的可重入次数,默认情况state为0;

  当第一次一个线程CAS成功获取了该锁,那么state就加一,当这个已经获取该锁的线程继续获取锁,state继续加一,释放锁state就减一,直到变为0,那么说明该锁就没有线程占有了,此时AQS中阻塞队列中的某个线程就可以获取锁了;

 

  我们可以看看这里的Sync实现了一些什么方法,下图所示,只有lock()方法没有实现,留给NonfairSync和FairSync各自根据自己的场景去实现;

 

 

  公平锁策略实现如下,实现了lock方法还有tryAcquire()方法,其中这个tryAcquire方法以前说活是AQS中特意留给子类根据实际场景去实现的

 

  非公平策略实现如下,和公平策略实现的方法一样;

 

三.非公平策略获取锁

  前面说了ReentrantLock的基本结构,然后我们分析看看是怎么获取锁的,从NonfairSync中的lcok方法开始,我们梳理一下:首先当前线程A使用CAS尝试将AQS中的state从0设置为1,如果成功,那就直接将当前线程设置为锁的占有者;失败的话,那就去获取state的值,如果为0,还是用CAS设置为1,将当前线程设置为锁的占有者,不为0,那就看看占有锁的是不是当前线程;如果是当前线程,就将state加一;不是当前线程,那就是其他线程占用该锁,这才真正说明当前线程获取锁失败,我们才将当前线程封装成一个Node.EXCLUSIVE类型的节点,丢到阻塞队列中去;

  这里我们需要想一下为什么说这里是非公平的?假如在下面nonfairTryAcquire方法中,线程A获取到的c的值为1,占有锁的不是线程A,那么线程就被封装成节点丢到阻塞队列中去了,这个时候线程B来获取c的值,刚好是0(因为这个时候可能那个占有了该锁的线程释放了锁),于是线程B就能成功的将state从0变为1,能获得该锁;

  虽然线程A先尝试获取该锁,线程B后去获取,然而线程B居然可以先成功获取到锁,就好像你排队吃饭,一个后来的人居然先打到饭去吃了,真的是日了狗了!

复制代码
final void lock() {
    //尝试通过CAS将state从0改为1,如果成功那就将当前线程占用该锁
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    //CAS失败,说明有其他线程已经获取锁了,于是就将当前线程丢到阻塞队列中去,重点看看acquire方法
    else
        acquire(1);
}

//主要看tryAcquire方法,在NonfairSync中实现
//当前方法中,if判断中第二个条件已经看过了,主要是将当前线程封装成一个Node.EXCLUSIVE类型的节点
//然后丢到阻塞队列的最后面去
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    //获取state的值
    int c = getState();
    //state的值位0的话,那就CAS设置为1,并且设置当前线程占用该锁
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //CAS设置失败,肯定有其他线程占用该锁,由于是可重入锁,所以这里先判断占有该锁的是不是当前线程
    else if (current == getExclusiveOwnerThread()) {
        //是,那就将state加一
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    //能到这里,说明state的值不是0,占有锁的也不是当前线程,肯定是其他线程,那就返回false
    return false;
}
复制代码

 

四.公平策略获取锁

  其实公平策略和非公平策略的实现基本一样,我们只看看tryAcquire方法的实现:

复制代码
protected final boolean tryAcquire(int acquires) {
    final Thread current =