登录 |  注册
首页 >  编程语言 >  Java常用类.方法及实例手册(API手册) >  显式锁ReentrantLock

显式锁ReentrantLock

学习synchronized关键字的时候我们了解了synchronized锁和它的膨胀过程,虽然Jdk1.5之后对synchronized进行了大量的优化,但是在使用synchronized过程中依然会存在很多问题。因此在Jdk1.5版本以后添加了显示锁Lock。

显式锁Lock

Lock接口是对锁操作的基本定义,它提供了synchronized关键字所具备的全部功能方法,另外我们可以借助Lock创建不同的Condtion对象进行多线程间的通信操作。

Lock接口的定义的方法如下所示:

java.jpg

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图:

java.jpg

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);
    }
}

非公平锁加锁流程图:

java.jpg

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());
    }

公平锁加锁流程图:

java.jpg

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了。

公平锁与非公平锁的区别:

  • 公平锁:当一个线程尝试获取锁的时候先加入同步队列,如果是下一个需要唤醒的节点则去竞争锁,锁的竞争是先到先得,保证了公平性,但是锁的竞争效率会变低。

  • 非公平锁:当一个线程尝试获取锁的时候优先尝试获取锁,如果获取失败再加入同步队列,提高了锁竞争的性能,但是会出现同步队列中的线程一直获取不到锁的现象,称为饥饿现象。

上一篇: toString()方法
下一篇: HttpServletRequest.getParameter()的用法
推荐文章
  • 在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编
学习大纲