• Lock


    之前对于synchronized的实现和运用有所了解,但是也发现了一些缺陷,由于synchronized是在JVM层面上实现同步互斥,是以关键字形式加锁,导致其颗粒度过大,有的时候不需要这么大范围的加锁,在中断和线程阻塞处理上也有所欠缺,在JDK1.5之后,Java引入了Lock,作为对于synchronized的补充。

    实现原理

    Lock是Java的一个接口类,而继承它实现的也是一个Java类,因此Lock的实现不是基于JVM的,而是在应用层,接口源码如下

    public interface Lock {
    	void lock();
    	void lockInterruptibly() throws InterruptedException;
    	boolean tryLock();
    	boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    	void unlock();
    	Condition newCondition();
    }
    

    这几个方法就是Lock的核心实现。而Lock的实现类是ReentrantLock,这里需要讲一下ReentrantLock的结构,ReentrantLock把对Lock的实现交给了继承AbstractQueuedSynchronizer抽象类的Sync类,而为了实现公平锁和和非公平锁,又加入了FairSyncNonfairSync两个继承Sync的类来重写lock()方法。

    • abstract static class Sync extends AbstractQueuedSynchronizer
    • static final class NonfairSync extends Sync
    • static final class FairSync extends Sync

    而ReentrantLock的加锁解锁原理则是利用AbstractQueuedSynchronizer类的方法,把所有的请求线程构成一个CLH队列(虚拟队列,将每个线程内容包装成一个Node类),运行线程不包括在内,如果有新进程加入,则阻塞并加入到队列尾部,如果运行线程结束,就从队列中获取下个线程。
    CLH
    在了解加锁和解锁过程之前首先需要了解Lock怎么阻塞线程,源码中显示调用了LockSupport.park(),在深入结果是调用sun.misc.Unsafe.park()本地方法,从而调用系统互斥锁。下面就是加锁和解锁的过程

    加锁

    对于加锁过程,非公平锁和公平锁机制不同,这里分开描述。
    非公平锁

    • 在开始就先尝试当前线程获取锁,如果成功,那么直接将锁锁定,不成功再进入队列调用的流程。

    • 进入队列调用的第一步是获取当前锁的状态,如果锁状态值为0,尝试获取锁,如果成功的话,将锁赋给当前线程,失败就阻塞后进入队列尾部排队。

    • 如果锁状态值为1,先看是否为存在重入锁的情况,即线程和运行线程相同,如果是,那么锁状态加1,如果不是,那么阻塞后进入队列尾部排队。

        //ReentrantLock.java文件
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//获取当前线程
            int c = getState();//获取锁状态值
            if (c == 0) { //如果锁状态为0,那么没有线程占用锁
                if (compareAndSetState(0, acquires)) {//CAS尝试获得锁
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果锁状态不为0
            else if (current == getExclusiveOwnerThread()) {//如果当前线程就是锁的线程,进入重入锁状态
                int nextc = c + acquires;//锁状态值加1
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
        //AbstractQueuedSynchronizer.java文件
        public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
        }
      

    公平锁

    • 不尝试获取锁,直接进入队列流程

    • 在队列流程中,同样获取锁的状态值,如果状态值为0,并且队列中没有排队的线程,那么尝试获取锁,如果有线程,那么就阻塞线程并放在队列尾部排队

    • 如果状态值不为0,先看是否为存在重入锁的情况,即线程和运行线程相同,如果是,那么锁状态加1,如果不是,那么阻塞后进入队列尾部排队。

         //ReentrantLock.java文件
         final void lock() {
            acquire(1);
        }
      
        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            //如果锁状态值为0和队列中没有线程,获取锁
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //重入锁情况
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
        //AbstractQueuedSynchronizer.java文件
        public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
        }
      

    解锁

    • 获取当前线程的状态值,然后减1

    • 如果结果为0,那么释放这个锁,如果不为0,那么存在重入锁情况,不释放锁

        //ReentrantLock.java文件
        public void unlock() {
        sync.release(1);
        }
        protected final boolean tryRelease(int releases) {
            //状态值-1
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //判断重入锁情况
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
        //AbstractQueuedSynchronizer.java文件
        public final boolean release(int arg) {
        if (tryRelease(arg)) {
        //获取排队线程的头线程,运行线程
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
        }
      

    Lock应用

    Lock接口有lock()、unlock()、tryLock()、tryLock(long time, TimeUnit unit) 和 lockInterruptibly()这些方法进行加锁和解锁,这里逐个分析(Condition()用于线程间通信,这里暂时不解释)

    lock()和unlock()

    这两个方法基本都是一起出现的,因为这里放在一起讲,lock()就是用来对代码块进行加锁,而unlock()是对锁的释放,而在它们之间就是加锁的代码,一般会加上try...catch方式,而unlock()一般会出现在finally中,基本格式如下

    Lock lock = new Lock();
    lock.lock();
    try{
    //加锁代码块
    ...
    }catch(Exception e){
    }finally{
    lock.unlock();   //释放锁
    }
    

    tryLock()和tryLock(long time, TimeUnit unit)

    tryLock()是有返回值的,如果当前锁未被占用,那么就尝试获取锁,这里和lock()一样,并返回true,但是如果锁被占用,那么直接返回false,不阻塞在那里,这是和synchronized区别部分之一,是对加锁的灵活运用。
    tryLock(long time, TimeUnit unit)是加上了等待时间,就是在tryLock()基础上加上了尝试获取的等待时间,如果锁获取失败,会等待一段时间,如果等待过程中获取到锁,那么返回true,如果还是没有,那么返回false。一般格式如下

    Lock lock = new Lock();
    if(lock.tryLock()) {
     try{
         //加锁代码块
    ...
     }catch(Exception e){
     }finally{
         lock.unlock(); 
     } 
    }else {
    //不加锁的处理
    ...
    }  
    

    lockInterruptibly()

    lockInterruptibly()和lock()的机制是一样的,如果没有特殊情况,就是一般的加锁,但是如果在加锁后使用Thread.interrupt中断线程,就会抛异常InterruptedException,因为lock优先考虑获取锁,在等待获取锁的过程中,忽略中断请求,只有成功获得锁之后才响应中断。lockInterruptibly允许在等待获取锁的过程中由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回。

    Lock lock = new Lock();
    lock.lockInterruptibly();
    try{
    //加锁代码块
    ...
    }catch(InterruptedException e){
    }finally{
    lock.unlock();   //释放锁
    }
  • 相关阅读:
    reduce常规教程
    新的职业计划
    vscode插件 console helper
    webpack的loader和plugin的区别
    for in 和for of的区别
    https://www.codegrepper.com/index.php
    防抖和节流
    千万级别的表分页查询非常慢,怎么办?
    https://gitee.com/knif/AcceleratorKunn?_from=gitee_search
    17个可以实现微前端的方案
  • 原文地址:https://www.cnblogs.com/xudilei/p/6850015.html
Copyright © 2020-2023  润新知