Java 中锁的分类,你知道几种?

1、重入锁

支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁

实现重进入需要解决的2个问题

(1) 线程再次获取锁:锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取

        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;
        }

该方法增加了再次获取同步状态的处理逻辑:通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true,表示获取同步状态成功。成功获取锁的线程再次获取锁,只是增加了同步状态值

(2) 锁的最终释放

线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放。


      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;
        }

如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false,而只有同步状态完全释放了,才能返回true。可以看到,该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。

2.乐观锁和悲观锁

(1) 悲观锁

对数据被外界修改保持保守态度,认为数据很容易就会被其他线程修改,所以修改在数据被修改前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态。

(2) 乐观锁

认为数据在一般情况下不会造成冲突,所以在访问记录前不会加排他锁

3. 公平锁和非公平锁的区别

(1) 公平锁

表示线程获取锁的顺序是按照线程请求锁的时间早晚来决定的
ReentrantLock pairLock = new ReentrantLock(true);

(2) 非公平锁

先来不一定先得

ReentrantLock pairLock = new ReentrantLock(false);如果不传,默认为非公平的

4.独占锁与共享锁

(1) 独占锁

保证任何时候都只有一个线程能获得锁,ReentrantLock就是以独占方式实现的

(2)共享锁

可以同时由多个线程持有,ReadWriteLock读写锁,它允许一个资源可以被多线程同时 进行读操作

5.读写锁

之前提到锁(如MutexReentrantLock)基本都是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。
除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁能够简化读写交互场景的编程方式。假设在程序中定义一个共享的用作缓存数据结构,它大部分时间提供读服务(例如查询和搜索),而写操作占有的时间很少,但是写操作完成之后的更新需要对后续的读服务可见。

在没有读写锁支持的(Java 5之前)时候,如果需要完成上述工作就要使用Java的等待通知机制,就是当写操作开始时,所有晚于写操作的读操作均会进入等待状态,只有写操作完成并进行通知之后,所有等待的读操作才能继续执行(写操作之间依靠synchronized关键进行同步),这样做的目的是使读操作能读取到正确的数据,不会出现脏读。改用读写锁实现上述功能,只需要在读操作时获取读锁,写操作时获取写锁即可。当写锁被获取到时,后续(非当前写操作线程)的读写操作都会被阻塞,写锁释放之后,所有操作继续执行,编程方式相对于使用等待通知机制的实现方式而言,变得简单明了。
一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量

ReentrantReadWriteLock其实实现的是ReadWriteLock接口

** 怎样使用读写锁实现缓存**


public class Cache {
    
    

    static Map<String,Object> map = new HashMap<>();

    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对应的vlaue ,并返回旧的value
    public static  final Object put(String key,String 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();
        }
    }


}

史上最全的并发编程脑图:https://www.processon.com/view/5b1f1ad7e4b03f9d251c06e5#map

猜你喜欢

转载自blog.csdn.net/fd2025/article/details/108359829