校招面试准备——Lock, ReadWirteLock接口

本文探讨了Java中synchronized的局限性,并介绍了Lock接口和ReadWriteLock接口的引入,以解决这些问题。Lock提供了更灵活的锁机制,如tryLock()和lockInterruptibly()方法,以支持在获取不到锁时进行中断。ReadWriteLock接口允许多个线程同时读取,提高并发性能。ReentrantLock作为Lock的实现,具有可重入性和公平/非公平选择。同时,ReentrantReadWriteLock提供读锁和写锁,允许细粒度的权限控制。
摘要由CSDN通过智能技术生成

之前学习了synchronized关键字,它是用于对 对象 加锁 实现同步。https://blog.csdn.net/wenqiao1/article/details/108385759

java1.5后新增了Lock,ReadWriteLock接口以及一些实现了lock接口的类;它也是用于实现各种锁(可重入锁,读写锁)

之所以又新加了这些接口,是因为synchronized存在一些缺陷:

1. 当一个线程通过synchronized加上锁以后,其他的线程只能一直等待它释放锁 而不能做别的,而这个线程只有在三种情况下释放锁:

       a. 代码块(方法)执行完了

       b. 占有锁的线程发生了异常,JVM要求它释放锁

       c. 占有锁的线程进入waiting状态而释放锁,例如在代码块(方法)中调用wait()方法

针对这个缺陷,Lock提供了tryLock()方法,能够使线程在获得不到锁的时候,等待一段时间 如果等不到就中断

此外,它还提供了lockInterruptibly()方法,去中断线程。

2. 当多个线程对同一个文件进行读操作的时候,多个线程同时读 实际上并不会发生冲突。但如果使用synchroized关键词,每个

线程都会上锁直到读完,而不会使线程们一起读取,这样消耗了时间。这时候ReadWriteLock接口的优势就体现出来了。

3. 在使用synchronized关键字时,我们(程序员)无法得知某个线程是否获得了锁。但Lock可以

 

Lock的缺点:

需要程序员主动释放锁,即使发生异常也不会释放锁,一般会要求将释放锁的代码写入finally{}块中

 

Lock 接口中的方法:

lock()   tryLock()  tryLock(long time, TimeUnit unit)  lockInterruptibly()   这四个方法都是用来获取锁的。

1. void lock() 获取锁,如果锁已经被其他线程获取,则线程要进行等待。

2. boolean tryLock()        boolean tryLock(long time, TimeUnit unit) 

    这两个方法是有返回值的,他们尝试获得锁,如果获取成功,则返回true;否则返回false

    boolean tryLock(long time, TimeUnit unit) 会在没获取锁之后 等待一定义时间,如果在期限内如果拿不到锁,就返回false

3. void lockInterruptibly() throws InterruptedException;

通过这个方法获取锁的时候,如果锁可用,会获得锁并立即返回true;否则会休眠一直到这两种事件之一发生:

       a.  等到了锁

       b. 有其他的线程中断了该这个线程 (threadB.interrupt()): 注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为interrupt()方法只能中断阻塞过程中的线程而不能中断正在运行过程中的线程。因此,当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,那么只有进行等待的情况下,才可以响应中断的。

4. void unlock()  释放锁

5. Condition newCondition()

 

实现了Lock接口的类:ReentrantLock

这个锁时可重入锁。在创建对象的时候通过参数决定它是否为公平锁,默认为非公平的。(synchronized为非公平的)

ReentrantLock实际上主要是AQS实现的,这个类有一个成员变量sync,它是Sync类型的,而Sync是一个ReentrantLock内部类,它继承了AQS,因为ReentrantLock是独占锁,所以它实现了AQS的tryRelease()方法。

        protected final boolean tryRelease(int releases) {
            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;  //返回资源是否真的被释放;有可能只是重入的线程释放了内层的锁
        }

但这个类依然是一个抽象类;它有两个子类:fairSync 和 NonfairSync。刚刚提到sync实现了AQS的tryRelease方法,那获取资源的方法在哪儿实现的呢?就是在这两个子类中,分别实现了公平和非公平时的获取资源的方法 tryAcquire()。且这两个类只实现了这一个方法。也就是说不论锁是公平锁还是非公平锁,释放锁的代码都是一样的,但是获得锁是不一样的。

获得锁的代码差距其实只是在state==0的时候,公平锁需要调用 AQS 的 hasQueuedPredecessors() 方法,这个方法判断了队列中是否有其他的线程在该线程之前,如果有则返回true;按照对来顺序来说,当前的这个线程不能获取锁,因为还没轮到它。如果该线程是头一个或者队列为空,则返回false

     static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires); //调用了父类的方法
        }
    }

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        @ReservedStackAccess
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState(); //查看现在是否有线程获得锁
            if (c == 0) {//如果没有 通过CAS修改state的值,因为用户只能调用lock(),因此acquires一定为1
                if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                    // 公平和非公平的区别就是判断 hasQueuedPredecessors
                    setExclusiveOwnerThread(current);
                    return true;  //有返回值,但是lock()方法不会处理这个返回值
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                //如果已经被占用了,因为是可重入锁,所以判断一下是不是获取所得线程重入
                int nextc = c + acquires;//如果是,再给state+1
                if (nextc < 0) //重入次数太多了,overflow了
                    throw new Error("Maximum lock count exceeded");
                setState(nextc); //走到这儿也不会有线程竞争啦,因为只要是其他线程,就被if判断拦住了
                return true;
            }
            return false;
        }
    }
        // 这是Sync的方法,非公平锁占用锁
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    public final boolean hasQueuedPredecessors() {
        Node h, s;
        if ((h = head) != null) {
            if ((s = h.next) == null || s.waitStatus > 0) {
                s = null; // traverse in case of concurrent cancellation
                for (Node p = tail; p != h && p != null; p = p.prev) {
                    if (p.waitStatus <= 0)
                        s = p;
                }
            }
            if (s != null && s.thread != Thread.currentThread())
                return true;
        }
        return false;
    }

接着讨论ReentrantLock(),它有一个成员函数  private final Sync sync; 当ReenrantLock()的构造函数没有参数时,会默认将sync初始化为非公平的AQS(NonfairSync);如果有参数,参数为true时就是公平的,否则是非公平的

因为AQS中有一个重要的成员变量 state,ReentrantLock也继承了这个变量。在ReentrantLock中,state为0时表示目前没有线程占用资源,为正数时表示有线程占用资源。因为这是独占模式的,所以当state大于1时,说明占用资源的线程重入了。

因为ReentrantLock实现了Lock接口,所以他要实现Lock接口中的方法。  回忆一下:acquire方法 中先调用tryAcquire,然后如果获取锁失败,调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 将线程入队列,一直等待获取锁;所以lock方法在线程没有获取锁的时候一直尝试和等待获取锁

    public void lock() {
        sync.acquire(1);
    }

    public void unlock() {
        sync.release(1);
    }

首先是 lock和unlock,可以看出这都是通过调用sync(AQS)的 acquire 和 release方法实现的。

再之后是两个tryLock方法:

没有参数的tryLock方法在尝试获取锁失败后就会返回,因此这里没有调用AQS的acquire方法,因为acquire方法会调用acquireQueued()方法将线程入队列 且等待。只是调用nonfairTryAcquire尝试一次,获取不到就放弃 返回false

有参数的tryLock我还没有学到

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }


    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

  

ReadWriteLock接口 // 链接一个很好的博客https://www.cnblogs.com/xiaoxi/p/9140541.html

这个接口一共就俩方法:Lock readLock()    Lock writeLock(),它们分别返回一个读锁和写锁。获得锁之后,用户可以调用读锁和写锁的方法。

ReentrantReadWriteLock 类实现了ReadWriteLock接口,这个类中也有一些内部类,比如Sync类(继承了AQS)、fairSync(公平锁),nonFairLock(非公平锁),ReadLock和WriteLock。

    abstract static class Sync extends AbstractQueuedSynchronizer {}
    static final class NonfairSync extends Sync {}
    static final class FairSync extends Sync {}
    public static class ReadLock implements Lock, java.io.Serializable {}
    public static class WriteLock implements Lock, java.io.Serializable {}

线程进入读锁的前提条件:

    没有其他线程的写锁,

    没有写请求或者有写请求,但调用线程和持有锁的线程是同一个。

线程进入写锁的前提条件:

    没有其他线程的读锁

    没有其他线程的写锁

Sync是一个实现了AQS的抽象类。首先它里边有一个内部类 HoldCounter,它主要有两个属性,count和tid,其中count表示某个读线程重入的次数,tid表示该线程的tid字段的值,该字段可以用来唯一标识一个线程。

// 计数器
static final class HoldCounter {
    // 计数
    int count = 0;
    // Use id, not reference, to avoid garbage retention
    // 获取当前线程的TID属性的值
    final long tid = getThreadId(Thread.currentThread());
}

这个Sync中,state(int类型,32位)记录了读锁和写锁的个数。同步状态在重入锁的实现中是表示被同一个线程重复获取的次数,即一个整形变量来维护,但是之前的那个表示仅仅表示是否锁定,而不用区分是读锁还是写锁。而读写锁需要在同步状态(一个整形变量)上维护多个读线程和一个写线程的状态。

读写锁对于同步状态的实现是在一个整形变量上通过“按位切割使用”:将变量切割成两部分,高16位表示读,低16位表示写。

http://static.open-open.com/lib/uploadImg/20151031/20151031223319_397.png

和state有关的一些其他的成员变量:

abstract static class Sync extends AbstractQueuedSynchronizer {
    // 版本序列号
    private static final long serialVersionUID = 6317671515068378041L;        
    // 高16位为读锁,低16位为写锁
    static final int SHARED_SHIFT   = 16;
    // 读锁单位
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    // 读锁最大数量
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    // 写锁最大数量
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
    // 本地线程计数器
    private transient ThreadLocalHoldCounter readHolds;
    // 缓存的计数器
    private transient HoldCounter cachedHoldCounter;
    // 第一个读线程
    private transient Thread firstReader = null;
    // 第一个读线程的计数
    private transient int firstReaderHoldCount;
}

ReadLock 和 WriteLock中都有一个AQS,这两个类分别实现了 tryAcquire() tryRelease()  以及 tryAcquireShared()  tryReleaseShared() 方法。因为这两个类都继承了Lock接口,因此它们也需要实现Lock接口中的5个方法(不回忆了哈)

首先是 lock() 和 unlock() 方法,它俩其实和之前的ReentrantLock一样,都是调用AQS的方法实现的

public void lock() {
    sync.acquire(1);
}

public void unlock() {
    sync.release(1);
}

首先先看写锁:

tryAcquire:

protected final boolean tryAcquire(int acquires) {
     //当前线程
     Thread current = Thread.currentThread();
     //获取状态
     int c = getState();
     //写线程数量(即获取独占锁的重入数)
     int w = exclusiveCount(c);
     //其中exclusiveCount方法表示占有写锁的线程数量,源码如下:
     //static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
     
     //当前同步状态state != 0,说明已经有其他线程获取了读锁或写锁
     if (c != 0) {
         // 当前state不为0,此时:如果写锁状态为0说明读锁此时被占用返回false;
         // 如果写锁状态不为0且写锁没有被当前线程持有返回false
         if (w == 0 || current != getExclusiveOwnerThread())
             return false;
         
         //判断同一线程获取写锁是否超过最大次数(65535),支持可重入
         if (w + exclusiveCount(acquires) > MAX_COUNT)
             throw new Error("Maximum lock count exceeded");
         //更新状态
         //此时当前线程已持有写锁,现在是重入,所以只需要修改锁的数量即可。
         setState(c + acquires);
         return true;
     }
     
     //到这里说明此时c=0,读锁和写锁都没有被获取
     //writerShouldBlock表示是否阻塞; 公平锁和非公平锁是不一样的
     if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
         return false;
     
     //设置锁为当前线程所有
     setExclusiveOwnerThread(current);
     return true;
 }

 tryRelease:

protected final boolean tryRelease(int releases) {
    //若锁的持有者不是当前线程,抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //写锁的新线程数
    int nextc = getState() - releases;
    //如果独占模式重入数为0了,说明独占模式被释放
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        //若写锁的新线程数为0,则将锁的持有者设置为null
        setExclusiveOwnerThread(null);
    //设置写锁的新线程数
    //不管独占模式是否被释放,更新独占重入数
    setState(nextc);
    return free;
}

读锁:

protected final boolean tryRelease(int releases) {
    //若锁的持有者不是当前线程,抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //写锁的新线程数
    int nextc = getState() - releases;
    //如果独占模式重入数为0了,说明独占模式被释放
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        //若写锁的新线程数为0,则将锁的持有者设置为null
        setExclusiveOwnerThread(null);
    //设置写锁的新线程数
    //不管独占模式是否被释放,更新独占重入数
    setState(nextc);
    return free;
}
protected final int tryAcquireShared(int unused) {
    // 获取当前线程
    Thread current = Thread.currentThread();
    // 获取状态
    int c = getState();

    //如果写锁线程数 != 0 ,且独占锁不是当前线程则返回失败,因为存在锁降级
    if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
        return -1;
    // 读锁数量 源码如下:
    // static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    int r = sharedCount(c);
    /*
     * readerShouldBlock():读锁是否需要等待(公平锁原则)
     * r < MAX_COUNT:持有线程小于最大数(65535)
     * compareAndSetState(c, c + SHARED_UNIT):设置读取锁状态
     */
     // 读线程是否应该被阻塞、并且小于最大值、并且比较设置成功
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        //r == 0,表示第一个读锁线程,第一个读锁firstRead是不会加入到readHolds中
        if (r == 0) { // 读锁数量为0
            // 设置第一个读线程
            firstReader = current;
            // 读线程占用的资源数为1
            firstReaderHoldCount = 1;
        } else if (firstReader == current) { // 当前线程为第一个读线程,表示第一个读锁线程重入
            // 占用资源数加1
            firstReaderHoldCount++;
        } else { // 读锁数量不为0并且不为当前线程
            // 获取计数器
            HoldCounter rh = cachedHoldCounter;
            // 计数器为空或者计数器的tid不为当前正在运行的线程的tid
            if (rh == null || rh.tid != getThreadId(current))
                // 获取当前线程对应的计数器
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0) // 计数为0
                //加入到readHolds中
                readHolds.set(rh);
            //计数+1
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
// 在tryAcquireShared函数中,如果下列三个条件不满足(读线程是否应该被阻塞、小于最大值、比较设置成功)
// 则会进行fullTryAcquireShared函数中,它用来保证相关操作可以成功。
}
protected final boolean tryReleaseShared(int unused) {
    // 获取当前线程
    Thread current = Thread.currentThread();
    if (firstReader == current) { // 当前线程为第一个读线程
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1) // 读线程占用的资源数为1
            firstReader = null;
        else // 减少占用的资源
            firstReaderHoldCount--;
    } else { // 当前线程不为第一个读线程
        // 获取缓存的计数器
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current)) // 计数器为空或者计数器的tid不为当前正在运行的线程的tid
            // 获取当前线程对应的计数器
            rh = readHolds.get();
        // 获取计数
        int count = rh.count;
        if (count <= 1) { // 计数小于等于1
            // 移除
            readHolds.remove();
            if (count <= 0) // 计数小于等于0,抛出异常
                throw unmatchedUnlockException();
        }
        // 减少计数
        --rh.count;
    }
    for (;;) { // 无限循环
        // 获取状态
        int c = getState();
        // 获取状态
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc)) // 比较并进行设置
            // Releasing the read lock has no effect on readers,
            // but it may allow waiting writers to proceed if
            // both read and write locks are now free.
            return nextc == 0;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值