显式锁ReentrantLock

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

《synchronized关键字》

目录

显式锁Lock

ReentrantLock介绍

ReentrantLock内部类分析

ReentrantLock用法

ReentrantLock总结


显式锁Lock

 

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

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

图片

lock():尝试获取锁。如果锁已经被另一个线程持有,那么该线程会进入阻塞状态,直到获取到锁。

lockInterruptibly():尝试获取锁,进入阻塞的线程是可以被中断的,该方法可以获取中断信号。

tryLock():尝试获取锁,调用该方法获取锁无论是否成功都会立即返回,线程不会进入阻塞状态。

boolean tryLock(long time, TimeUnit unit):该方法与tryLock()方法类似,只是多了获取锁时间的限制,如果在限制的时间内没有获取到锁,则结果返回false。

unlock():释放锁,在持有锁的线程运行结束后,应该确保对锁资源的释放。

newCondition():创建一个与Lock相关的Condition对象。

显示锁Lock有很多实现:ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock。开发中最常用的实现类是ReentrantLock。

所以接下来我们就开始学习ReentrantLock啦!

 

ReentrantLock介绍

 

ReentrantLock是基于AQS同步器构建的锁,只支持独占方式的获取操作,因此它实现了tryAcquire、tryRelease、isHeldExclusively。

ReentrantLock将同步状态state用于保存锁获取操作的次数,并且还维护了一个owner变量用来保存当前所有者线程的标识符。

ReentrantLock的UML图:

图片

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

非公平锁加锁流程图:

图片

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

公平锁加锁流程图:

图片

 

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

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

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

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

    我是勾勾,一直在努力的程序媛!感谢您的点赞、转发和关注!

    参考文档:

    《Java并发编程实战》

    《Java高并发编程详解》

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值