10.Lock

StampedLock

1. Lock接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UFs3MUn2-1577708089191)(https://note.youdao.com/yws/res/41080/4B25E52EC8AB413AA024C2664860A2C8)]
在 Lock 接口出现之前,Java 程序是靠 synchronized 关键字实现锁功能的,而 Java SE 5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,它提供了与 synchronized 关键字类似的同步功能,只是在使用时需要显式地获取和释放锁
缺点:缺少了隐式获取释放锁的便捷性
优点:拥有锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种 synchronized 关键字所不具备的同步特性

1.1. Lock的简单使用

    Lock lock = new ReentrantLock();
    lock.lock();
    try {
        //业务逻辑
    } finally {
        lock.unlock();
    }

1.2. Lock接口说明

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}
方法名含义
lock()尝试获取锁,如果获取到锁返回否则阻塞等待
lockInterruptibly()可中断地获取锁,在锁的获取中可以中断当前线程
tryLock()非阻塞的获取锁,调用后立刻返回,如果获取成功返回true,失败返回false
tryLock(long time, TimeUnit unit)超时获取锁,成功返回ture,否则超时后返回false
unlock()释放锁,解锁
newCondition()获取等待通知组建,该组件和当前的锁绑定。只有当前线程获取到了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁。

1.3. ReentrantLock的加锁实现

  1. 公平锁能保证:老的线程排队使用锁,新线程仍然排队使用锁。
  2. 非公平锁保证:老的线程排队使用锁;新的线程先去抢占,抢占失败再去排队
公平锁                                                             非公平锁
final void lock() {                                                 final void lock() {
    acquire(1);                                                         if (compareAndSetState(0, 1))
}                                                                           setExclusiveOwnerThread(Thread.currentThread());
                                                                        else
                                                                            acquire(1);
                                                                    }

protected final boolean tryAcquire(int acquires) {                  final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();                       final Thread current = Thread.currentThread();
    int c = getState();                                                  int c = getState();
    if (c == 0) {                                                        if (c == 0) {
        if (!hasQueuedPredecessors() &&                                     if (compareAndSetState(0, acquires)) {
        compareAndSetState(0, acquires)) {                                          
            setExclusiveOwnerThread(current);                                   setExclusiveOwnerThread(current);
            return true;                                                        return true;
        }                                                                    }
    }                                                                     }
    else if (current == getExclusiveOwnerThread()) {                      else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;                                            int nextc = c + acquires;
        if (nextc < 0)                                                       if (nextc < 0) // overflow        
            throw new Error("Maximum lock count exceeded");                      throw new Error("Maximum lock count exceeded");
        setState(nextc);                                                     setState(nextc);
        return true;                                                         return true;
    }                                                                     }
    return false;                                                         return false;
}                                                                     }

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); 头不等于尾 且 (头没有后继节点或头的后继节点所属线程不是当前线程)
}

从上面我们可以看到 公平不公平的区别有两个

  • 非公平锁上来直接加锁,如果失败了才会调用tryAcquire
  • 公平锁在tryAcquire时只有没有前驱节点的时候才尝试取锁,而非公平锁不管三七二十一直接取锁,根本不管队列,取锁失败后会进入队列

2.ReadWriteLock接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-15G1WzKi-1577708089193)(https://note.youdao.com/yws/res/42056/FB1D0D208DE24984BD41DF885F516DFA)]

2.1. ReadWriteLock接口简单使用

public class Cache {
    static Map<String, Object> map = new HashMap<String, Object>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();
    // 获取一个 key 对应的 value
    public static final Object get(String key) {
        r.lock();
        try {
            return map.get(key);
        } finally {
            r.unlock();
        }
    }
    // 设置 key 对应的 value,并返回旧的 value
    public static final Object put(String key, Object value) {
        w.lock();
        try {
            return map.put(key, value);
        } finally {
            w.unlock();
        }
    }
    // 清空所有的内容
    public static final void clear() {
        w.lock();
        try {
            map.clear();
        } finally {
                w.unlock();
        }
    }
}

Cache 组合一个非线程安全的 HashMap 作为缓存的实现,同时使用读写锁的读锁和写锁来保证 Cache 是线程安全的。

在读操作 get(String key) 方法中,需要获取读锁,这使得并发访问该方法时不会被阻塞。
写操作 put(String key,Object value) 方法和 clear()方法,在更新HashMap时必须提前获取写锁,当获取写锁后,其他线程对于读锁和写锁的获取均被阻塞,而只有写锁被释放之后,其他读写操作才能继续
Cache 使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性,同时简化了编程方式

2.2. ReadWriteLock 接口说明

public interface ReadWriteLock {
    Lock readLock(); // 获取读锁对象
    Lock writeLock();// 获取写锁对象
}

2.3. ReentrantReadWriteLock 实现

写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升

  • 读锁 写锁 用的是同一个Sync也就是同一个AQS
  • AQS的初始化由ReentrantReadWriteLock在构造函数中进行
  • 默认构造非公平锁
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
    ...
    public ReentrantReadWriteLock.WriteLock writeLock() { 
        return writerLock; 
    }
    public ReentrantReadWriteLock.ReadLock  readLock()  { 
        return readerLock; 
    }
    abstract static class Sync extends AbstractQueuedSynchronizer {
        ...
    }
    public static class ReadLock implements Lock, java.io.Serializable {
        private final Sync sync;
        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
        ...
    }
    public static class WriteLock implements Lock, java.io.Serializable {
        private final Sync sync;
        protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
        ...
    }
    
    static final class NonfairSync extends Sync {
        final boolean writerShouldBlock() {
            return false; 
        }
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
    }

  
    static final class FairSync extends Sync {
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }
}

2.3.1.lock 加锁

  • 读锁 sync.acquireShared(1);
  • 写锁 sync.acquire(1);
    static int sharedCount(int c) {
        return c >>> SHARED_SHIFT;
    }
    static int exclusiveCount(int c) {
        return c & EXCLUSIVE_MASK; 
    }

读写锁将变量切分成了两个部分,高16位表示读,低16位表示写

  • sharedCount 通过位移得到高位的共享锁数量
  • exclusiveCount 通过& 得到低位的独占锁数量

写锁-独占锁

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState(); // 得到状态
    int w = exclusiveCount(c); // 得到独占写锁数量
    if (c != 0) { 
        // (Note: 如果总数 c 不是 0 同时 写数量 w 是0 所以r读锁数量不为0 代表有线程在读数据)
        if (w == 0 || current != getExclusiveOwnerThread()) {// 如果有读取中的线程,写锁获取失败 如果当前线程与持有锁的线程不是一个失败
            return false;
        }
        if (w + exclusiveCount(acquires) > MAX_COUNT) {// 如果写锁数量超过限制
            throw new Error("Maximum lock count exceeded");
        }
        // Reentrant acquire
        setState(c + acquires); // 如果当前线程已经获取了写锁,则增加写状态
        return true;
    }
    // 如果读写锁都没有 则尝试通过CAS的方式设置锁
    if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) {
        return false;
    }
    setExclusiveOwnerThread(current);
    return true;
}

读锁-共享锁

    protected final int tryAcquireShared(int unused) {
        Thread current = Thread.currentThread();
        int c = getState();
        // 如果有写锁 且 线程不是当前线程 加锁失败 也就是说持有读锁的线程可以继续获取写锁
        if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) { 
            return -1;
        }
        int r = sharedCount(c); // 得到读锁数量
        if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { // CAS方式修改State成功,也就是取锁成功
            if (r == 0) { // 如果读锁数量是0 那么 当前线程是第一个读取线程
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) { // 如果当前线程是第一个读取线程
                firstReaderHoldCount++;
            } else { // 如果当前线程不是第一个读取线程
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current)) {
                    cachedHoldCounter = rh = readHolds.get();
                } else if (rh.count == 0) {
                    readHolds.set(rh);
                }
                rh.count++; // 读取锁获取次数
            }
            return 1;
        }
        return fullTryAcquireShared(current);
    }
    
    final int fullTryAcquireShared(Thread current) {
        /*
         该代码与tryAcquireShared中的代码部分冗余,
         但由于不使tryAcquireShared与重试和延迟读取保持计数之间的交互复杂化,
         因此总体上更简单。
         */
        HoldCounter rh = null;
        for (;;) {
            int c = getState(); // 获取当前state
            if (exclusiveCount(c) != 0) { // 当前锁被写线程持有
                if (getExclusiveOwnerThread() != current) { // 如果当前线程不是持有锁的线程,无法获取读锁
                    return -1;
                }
            } else if (readerShouldBlock()) { // 如果当前线程没有被写线程持有 但是读取需要阻塞 获取锁失败
                if (firstReader == current) { // 如果当前线程是第一个读取者
                    // assert firstReaderHoldCount > 0;
                } else {
                    if (rh == null) {
                    
                        rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current)) {
                            rh = readHolds.get();
                            if (rh.count == 0) {
                                readHolds.remove();
                            }
                        }
                    }
                    if (rh.count == 0) {
                        return -1;
                    }
                }
            }
            if (sharedCount(c) == MAX_COUNT) {// 如果读取线程书来给你超过最大值
                throw new Error("Maximum lock count exceeded");
            }
            if (compareAndSetState(c, c + SHARED_UNIT)) { // 通过CAS增加读取锁状态 返回状态
                if (sharedCount(c) == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    if (rh == null) {
                        rh = cachedHoldCounter;
                    }
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                    } else if (rh.count == 0) {
                        readHolds.set(rh);
                    }
                    rh.count++;
                    cachedHoldCounter = rh; // cache for release
                }
                return 1;
            }
        }
    }

2.3.2. unlock解锁

写锁-独占锁

    public void unlock() {
        sync.release(1); // 释放独占锁
    }

读锁-共享锁

    public void unlock() {
        sync.releaseShared(1); // 释放共享锁
    }

2.3.3. 锁降级

锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。
锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。

锁降级要解决的问题
为什么要降级而不是分段完成呢,因为如果释放了写锁在获取读锁前可能有其他线程获取到写锁,并修改了数据,那么当前线程是无法感知的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值