美文网首页
Java并发问题-锁的使用-ReentrantLock原理

Java并发问题-锁的使用-ReentrantLock原理

作者: DoubleFooker | 来源:发表于2019-10-03 12:51 被阅读0次

java中JUC工具中提供了ReentrantLock实现灵活的加锁操作。
首先查下累的继承关系图,ReentrantLock实现跟图中的类息息相关


image.png

重入锁

允许相同线程获取锁后再次获得锁,不会阻塞线程,只需要记录获得锁的次数。
synchronized和reentrantLock都是可重入锁,注意ReentrantLock获取了N次锁同样需要释放N次锁。

ReentrantLock中的AQS(AbstractQueuedSynchronizer)

AQS是jvm中提供的同步工具,实现锁的独占与共享。
在AQS中维护了head、tail、state(锁状态)信息,AQS的父类包含属性exclusiveOwnerThread,标示持有锁的线程
head 和tail是Node节点,Node节点是一个双向链表,包含prev、next、waitStatus信息。
当线程尝试获取锁时,加锁操作通过CAS设置锁状态为1,并将exclusiveOwnerThread设置为自己,如果成功则获得锁。失败时会再进行一次尝试(因为锁可能很快就被释放),成功即获得锁。如果是相同的线程则state加1,标示冲入次数。失败则进入等待队列。当前想成会被分装成node节点,通过自旋的方式入队。此时如果等待队列为空,head、tail会先指向一个空节点,再通过cas将线程node节点的prev指向tail节点,tail节点的next指向线程node节点,最后将tail节点标记为线程node节点,完成队尾插入操作。
这时只是对node节点的封装,封装完成在进行一次锁竞争,失败则阻塞等待。node节点此时阻塞通过不断循环(注意这里会不断循环,下面讲到线程唤醒时会继续循环)的方式判断prev节点是否为头节点,并尝试获得锁。如果还是失败则进入等待的判断。等待判断当前节点的waitStatus是否为SIGNAL,如果是则循环阻塞等待,如果为CANCEL状态,则移除节点。其他状态则标记waitStatus为SIGNAL,这时还会判断线程是否为CANCEL状态,如果是从队尾节点开始(因为入队的过程中可能存在next未构造的情况,所以从尾部开始)删除节点,如果不是CANCEL状态,调用LockSupport.park()挂起线程。所以所有等待队列中的节点等待状态都为SIGNAL,并且都挂起。
当持有锁的线程调用unLock时,将AQS的state-1,当state==0是exclusiveOwnerThread设置为null,并设置state为0。此时获取head节点,设置waitStatus为0,并获取head的next节点,判断waitStatus部位CANCEL,则调用LockSupport.unPark唤醒线程,进行锁的竞争(因为此时可能还有队列外的线程竞争锁),如果成功者设置head为当前node节点,并将node的thread和prev设成null,标示head节点。
以上是ReentrantLock通过封装AQS实现加锁、解锁的功能。其他的如Condition也有使用AQS封装自己的功能,Condition实现的是单向队列,实现等待唤醒的功能。

ReentrantLock锁的过程

image.png

ReentrantLock与Synchronized的区别

  • 两者都是可重入锁
  • ReentrantLock可以更方便的自由控制锁的粒度,使用更灵活
  • synchronized在程序运行结束或异常是释放锁,ReentrantLock一般都在finally中释放
  • synchronized是非公平锁,ReentrantLock可公平可不公平。

拓展-ReentrantReadWriteLock

ReentrantReadWriteLock可以分别获取读锁和写锁,当有线程操作写锁是,其他线程才会阻塞,所有线程都只操作读锁是不会阻塞。

public class ReentranReadWriteLockDemo {
    static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    static Lock readLock = lock.readLock();
    static Lock writeLock = lock.writeLock();

    static void get() {
        readLock.lock();
        try {
            System.out.println("get");

        } finally {
            readLock.unlock();
        }
    }

    static void save() {
        // 这里会阻塞其他尝试lock的线程,不管是readLock还是writeLock
        writeLock.lock();
        try {
            System.out.println("save");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(() -> {
            ReentranReadWriteLockDemo.save();
        }).start();
        new Thread(() -> {
            ReentranReadWriteLockDemo.get();
        }).start();
    }
}

相关文章

网友评论

      本文标题:Java并发问题-锁的使用-ReentrantLock原理

      本文链接:https://www.haomeiwen.com/subject/qlglpctx.html