美文网首页
Lock(第三篇)

Lock(第三篇)

作者: salix_ | 来源:发表于2020-03-25 14:29 被阅读0次

    一:synchronized的弊端

    第二篇中我们介绍了synchronized,为啥有了synchronized还需要又Lock呢?

    1. synchronized无法手动释放锁。有些操作对于我们来说实现不了,比如说一个线程sleep的时候,需要让出锁,但synchronized我们是控制不了的。
    2. 无法实现读锁。两个线程同时读实现不了。

    二:简单介绍Lock

    Lock是一个接口,我们可以看见它下面又三个实现。ReentrantLock,Condition,ReadWriteLock。 Lock接口的抽象方法:
    然后我们开始介绍Lock下面重要实现。

    三:ReentrantLock

    1.是个class

    实现了Lock,说明是一个锁,实现了Serializable,说明了支持序列化。

    2.构造器(两个,对应公平锁、非公平锁)

    1. 首先是其内部有一个Sync类型的变量,Sync其实是一个ReentrantLock的抽象静态内部类。我们只用知道ReentrantLock内部操作其实是由Sync类型的变量sync来实现的即可。
    2. 然后看ReentrantLock的构造器。

      我们会发现两个构造器会给sync赋不同的对象引用,一个是FairSync,另一个是NonfairSync。很明显一个是公平锁一个是非公平锁。
    3. 公平锁和非公平锁:
    • 公平锁能保证:老的线程排队使用锁,新线程仍然排队使用锁。
    • 非公平锁保证:老的线程排队使用锁;但是无法保证新线程抢占已经在排队的线程的锁。

    3:方法

    1. 我们看ReentrantLock内部的实现就会发现,其实一直都是sync的方法调用。
    2. 所以现在我们除了知道ReentrantLock可以设置成公平锁、非公平锁之外,我们想要更深层次的了解,还是要深入看ReentrantLock的内部类FairSync和NonFairSync是怎样实现的。
    3. FairLock、NonFairLock其实是继承了Sync,看一下继承关系,我画的那个大框框就是ReentrantLock,有内部类Sync,FairSync,NonFairSync。

    4:顶顶大名AQS(看AbstractQueuedSynchronize源码)

    [https://www.zhihu.com/search?type=content&q=AQS]
    (这个链接的文章很不错)
    其实我也是学着源代码,写着博客。一直都找不到ReentrantLock的切入点,后来想起来了AQS,这就是个不错的切入点。

    1. AQS机制(AQS的功能分为两种:独占和共享)
      如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
    2. 其实AQS就是我们上面AbstractQueuedSynchronizer的缩写
    3. AQS的实现:
      AbstractQueuedSynchronizer内部有一个类Node,Node内部定义了是线程状态的常量,而且我们通过Node的构造器发现,每一个Node里面确实是由一个线程的。AQS的实现依赖内部的同步队列,也就是FIFO的双向队列,如果当前线程竞争锁失败,那么AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程。当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。

    5.总结:

    • ReentrantLock内部是是一个公平锁class+非公平锁class,两个class都是继承于Sync。Sync由实现了AbstractQueuedSynchronizer(AQS)。AQS由两种功能,一种是独占(例如ReentrantLock),另一种是共享(例如ReentrantReadWriteLock)。(独占的时候,这个锁只能由一个线程掌控,共享的时候,这个锁可以由多个线程公用。)
    • AQS内部是维护了一个FIFO的双向队列,如果当前线程竞争锁失败,那么AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程。当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。
    • AQS内部当然也是用了CAS技术,就是队列里添加元素的时候,是通过CAS实现的。

    四:ReentrantReadWriteLock (读写锁)

    参考了https://www.zhihu.com/search?type=content&q=ReentrantReadWriteLock

    1. 为啥引入ReentrantReadWriteLock

    很多并发情况下,之前介绍的锁机制例如ReentrantLock重入锁,只允许单个线程执行受保护的程序,而在大部分应用中都是读的操作次数远远大于写操作次数。如果使用ReentrantLock锁会严重影响整体的性能,如果使用ReentrantReadWriteLock可以实现并发访问的情况下,读可以多线程同时访问,而写只有一个线程可以执行。他允许多读,但是不允许读和写同时发生。(1)读-读不互斥:读读之间不阻塞(2)读-写互斥:读会阻塞写,写也会阻塞读(3)写-写互斥:写写阻塞

    2. 内部大体实现(和ReentrantLock一样都是AQS)

    和ReentrantLock一样,构造函数传参的时候,可以传进去一个boolean变量,指定是公平锁还是非公平锁(默认是非公平锁)。ReentrantReadWriteLock还继承了ReadWriteLock接口(ReadWriteLock内部只有两个方法)。
    ReentrantReadWriteLock 比 ReentrantLock内部多了一个WriteLock,ReadLock,顾名思义一个是读锁,一个是写锁。 Sync类型sync其实也是继承了AbstractedQueeuedSynchronizer。

    3.使用

    public class ReentrantReadWriteLockTest {
        private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        private final Lock readLock = readWriteLock.readLock();
        private final Lock writeLock = readWriteLock.writeLock();
    
        public String read() {
            readLock.lock();
            try {
                //执行的代码快
                return "xxx";
            } finally {
                readLock.unlock();
            }
        }
    
        public void write() {
            writeLock.lock();
            try {
                //执行写操作代码快
                System.out.println("执行写操作代码快");
            } finally {
                writeLock.unlock();
            }
        }
    }
    

    4. 读锁-读锁 写锁-写锁 写锁-读锁之间的关系是怎样处理的?

    ReentranLock里面已经说过了是怎样实现公平锁、非公平锁的

    然后我们只说说读锁-写锁的处理:
    ReentrantReadWriteLock中一个state怎么维持两种状态,其实很简单,state是个32位的int变量,那我们就分成两份,高16位表示读,低16位表示写,假设状态为S,读状态为S>>>16,写状态为S&0x0000FFFF。也是就说我是读锁变写锁就S>>16,写锁变读锁就是S<<16。

    5.总结

    理解了AQS再来看ReentrantLock、ReentReadWriteLock就觉得一目了然。

    相关文章

      网友评论

          本文标题:Lock(第三篇)

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