ReentrantLock等锁原理

面试准备,无法保证理解的正确性,慎,欢迎纠正

注意

这些锁是并发包下的锁,实现原理全是基于AQS,还使用了CAS操作,先了解一下AQS和CAS

ReentrantLock

  • 可重入锁,是一种独占锁,即同时只有一个线程可以获取锁,其他尝试获取锁的线程会被放在锁的AQS阻塞队列中,重入的意思是指同一个线程可以多次获取锁.
  • 公平锁和非公平锁. ReentrantLock内部有两个内部类NonfairSyncFairSync分别是非公平锁和公平锁,公平和不公平的区别在于后尝试的线程是否会在先尝试获取锁的线程前获取这个锁.具体的实现,下面说.
  • AQS的博客中说到过,state这个变量在不同的锁实现中有不同的意义,在ReentrantLock中,state变量代表一个线程获取锁的可重入次数,默认情况下为0,代表没有被线程持有,当第一个线程尝试使用CAS设置state为1,如果CAS成功,就记录锁的持有者为该线程,这个记录的操作是通过AQS中的setExclusiveOwnerThread(Thread)设置.在该线程没有释放锁的情况下第二次获取锁后,状态值被设置为2,即可重入次数.在该线程释放锁时,尝试CAS操作使state减一,如果减一后为0,当前线程释放锁.
  • lock,tryLock,lockInterruptibly
    • 这个是面试中的问题,三种获取锁的方式有什么不同
    • lock是获取锁,如果锁已经被其他线程持有,就将该线程封装置于AQS队列中阻塞挂起.该方法的公平和非公平策略有所不同
      • 非公平策略:先来的线程没有获取到锁被置于队列中挂起,后来的线程如果刚好碰到当前持有的锁的线程释放锁,就会直接获取锁,不会查看队列中是否有比自己先来的线程.
      • 公平策略就容易理解了,在能拿到锁的时候查看队列中是否有比自己先来的锁,如果有,会让出.
    • lockInterruptibly();与lock()类似,不过对中断响应,即如果使用该方法尝试获取时,如果其他线程调用了interrupt()方法中断该线程,该方法会抛出InterruptionException
    • tryLock();尝试获取锁,如果锁被持有,就直接返回false,不添加到队列,不挂起阻塞,否则获取锁,返回true;

ReentrantReadWriteLock

  • 可重入的读写锁,在某些方面和上一个锁还是有些类似的,读写锁内部维护了一个ReadLock和一个WriteLock,底层还是AQS,但是AQS只有一个state状态量,如何同时控制读和写呢,这里使用了state(int)的高16位表示读状态,低16为表示写,高16位的值代表获取读锁的线程数,低16位代表写锁的可重入数.

  • 读写锁维持了很多和读锁有关的变量:

    • Thread firstReader:第一个获取读锁的线程
    • int firstReaderHoldCount:记录第一个获取读锁的线程的可重入次数.
    • int cachedHoldCounter:最后一个获取读锁的线程的可重入次数
    • ThreadLocal readHolds:除了第一个获取读锁的线程以外的线程的可重入次数
  • 读写锁也有公平和非公平策略,与ReentrantLock大同小异,不详细介绍了

  • 写锁的获取与释放

    • void lock(); 写锁是一个独占锁,和ReentrantLock类似,如果有其他线程已经获得写锁,就进入队列挂起等待,否则获取锁或者使状态值加一.
    • 还有tryLock()和lockInterruptibly()以及一些带等待时限的方法,不介绍了,大同小异
    • void unlock();释放锁,状态值减一,为0后释放锁,如果没有持有锁而调用了释放锁,抛出IllegalMonitorStateException异常.
  • 读锁的获取和释放

    • void lock();如果当前没有其他线程持有写锁,可以获得读锁,高16为加一,方法返回,否则进入队列阻塞等待.
      • 需要注意的是,如果当前要获取读锁的线程已经持有了写锁,则也可以获取读锁,但要注意的是,先获取了写锁,然后获取读锁处理事情完毕后,读写锁都释放,不能只释放其一.
    • 获取读锁的实现不复杂,但是很麻烦,因为读写锁内部维持了很多读锁的变量,需要在获取读锁成功后修改.

待补充

你可能感兴趣的:(java,面试准备)