阿里架构师讲面试:Java Lock锁体系结构

524 阅读8分钟

我们已经知道,synchronized 是java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,但 synchronized 粒度有些大,在处理实际问题时存在诸多局限性,比如响应中断等。Lock 提供了比 synchronized更广泛的锁操作,它能以更优雅的方式处理线程同步问题。

Lock锁与Synchronized关键字比较

我们可以回顾一下synchronized的使用,synchronized释放锁的时机有以下几种:

  1. 当执行完代码块中的代码,释放锁;
  2. 当代码抛出异常,释放锁;
  3. 当调用锁的wait方法,释放锁;

以上三种情况便可以完全的帮我们解决线程同步的问题,为什么还要引入Lock呢?同样,synchronized关键字也有三种局限性:

  1. synchronized关键字无法响应中断,如果线程未获得锁,便会一直地尝试去获得锁,不会响应中断,lock锁的lockInterruptibly()方法能让线程响应中断,同时tryLock可以加入时间参数,若一定时间内未获得锁便返回,很灵活;
  2. synchronized关键字无法读写分离,在多个读线程访问临界资源时,是不需要同步的,但是synchronized关键字通通都给同步了,会导致效率很慢,Lock提供读写锁,实现读写分离;
  3. synchronized关键字无法知道线程是否获得锁;

以上为synchronized的局限性,但是Lock也有他的缺点,Lock需要显式的释放锁,否则会造成死锁。

Lock是一个接口,是JDK层面的实现;而synchronized是Java中的关键字,是Java的内置特性,是JVM层面的实现;

可重入锁

如果锁具备可重入性,则称作为 可重入锁 。像 synchronized 和 ReentrantLock 都是可重入锁,可重入性实际上表明了 **锁的分配粒度:**基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。

class MyClass {
    public synchronized void method1() {
        method2();
    }
    public synchronized void method2() {
    }
}

上述代码中的两个方法method1和method2都用synchronized修饰了。假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是,这就会造成死锁,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。

可中断锁

顾名思义,可中断锁就是可以响应中断的锁。在Java中,synchronized就不是可中断锁,而Lock是可中断锁。

如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。在前面演示tryLock(long time, TimeUnit unit)和lockInterruptibly()的用法时已经体现了Lock的可中断性。

公平锁

公平锁即 尽量 以请求锁的顺序来获取锁。比如,同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。而非公平锁则无法保证锁的获取是按照请求锁的顺序进行的,这样就可能导致某个或者一些线程永远获取不到锁。

在Java中,synchronized就是非公平锁(抢占锁),它无法保证等待的线程获取锁的顺序。而对于ReentrantLock 和 ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为 公平锁(协同式线程调度)

读写锁

读写锁将对临界资源的访问分成了两个锁,一个读锁和一个写锁。正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。

参考:blog.csdn.net/justloveyou…

ReentrantLock

ReentrantLock是Java在JDK1.5引入的显式锁,在实现原理和功能上都和内置锁(synchronized)上都有区别。

底层实现原理

ReentrantLock是基于AQS实现。AQS是一个抽象类,所以不能直接实例化,当我们需要实现一个自定义锁的时候可以去继承AQS然后重写获取锁的方式释放锁的方式****管理state,而ReentrantLock就是通过重写了AQStryAcquiretryRelease方法实现的lockunlock

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;

    /**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        @ReservedStackAccess
        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;
        }

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

使用方法

ReentrantLock,即可重入锁。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。

@Slf4j
public class LockDemo {
    private Lock lock=new ReentrantLock();

    public void baseUse(){
        lock.lock();
        try {
            Thread.sleep(3000);//模拟业务代码
        }catch (Exception e){
            log.error("发生异常:",e);
        }finally {
            lock.unlock();
        }
    }
}

解锁方法,一定在finally里,保证锁是一定能释放的,否则很容易引起死锁。

ReentrantReadWriteLock

读写锁并不是java的概念,是一个通用技术,所有的读写锁都遵从3大原则:

  1. 允许多个线程同时读变量。
  2. 只允许一个线程写变量。
  3. 如果一个线程正在写共享变量,禁止其他线程读这个共享变量。

底层实现原理

读写锁将对临界资源的访问分成了两个锁,一个读锁和一个写锁。正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。

伪代码如下:

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    /** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** Performs all synchronization mechanics */
    final Sync sync;

    public static class ReadLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -5992448646407690164L;
        private final Sync sync;
    }

    public static class WriteLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -4992448646407690164L;
        private final Sync sync;
    }

}

使用方法

public class UseReentrantReadWriteLock {
    private ReentrantReadWriteLock rwLock =
            new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    private ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
    public void read(){
        try {
            readLock.lock();
            System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
            Thread.sleep(3000);
            System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            readLock.unlock();
        }
    }
    public void write(){
        try {
            writeLock.lock();
            System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
            Thread.sleep(3000);
            System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            writeLock.unlock();
        }
    }
    public static void main(String[] args){
        final UseReentrantReadWriteLock urrw =
                new UseReentrantReadWriteLock();
        Thread t1 =
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        urrw.read();
                    }
                },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                urrw.read();
            }
        },"t2");
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                urrw.write();
            }
        },"t3");
        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
                urrw.write();
            }
        }, "t4");
        t1.start();
        //t3.start();
        //t4.start();
        t2.start();
    }
}

缓存器实现:

public class CacheDemo {
    //缓存器
    private Map<String,Object> map =
            new HashMap<>();
    private ReadWriteLock rwl = new ReentrantReadWriteLock();
    public Object get(String id) {
        Object value = null;
        rwl.readLock().lock();//首先开启读锁,从缓存中去取
        try {
            value = map.get(id);
            if (value == null) {//如果缓存中没有释放读锁,上写锁
                rwl.readLock().unlock();
                rwl.writeLock().lock();
                try {
                    if (value == null) {
                        value = "aaa";//此时可以去数据库中查找,这里简单的模拟一下
                    }
                } finally {
                    rwl.writeLock().unlock();//释放写锁
                }
                rwl.writeLock().unlock();//然后再上读锁
            }
        } finally {
            rwl.readLock().unlock();
        }
        return value;
    }
}

参考:www.jianshu.com/p/b52671244…

Condition类

底层实现原理

Condition是在java 1.5中才出现的。它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。

使用方法

  • Condition是个接口,基本的方法就是await()和signal()方法;
  • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
  • 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用。

Conditon中的await()对应Object的wait();

Condition中的signal()对应Object的notify();

Condition中的signalAll()对应Object的notifyAll()。

package thread;  

import java.util.concurrent.locks.Condition;  
import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReentrantLock;  
/** 
 *  
 * @author xg
 * 
 * 2020年9月1日 下午5:48:54 
 */  
public class ConTest {  

    final Lock lock = new ReentrantLock();  
    final Condition condition = lock.newCondition();  

    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        ConTest test = new ConTest();  
        Producer producer = test.new Producer();  
        Consumer consumer = test.new Consumer();  

        consumer.start();   
        producer.start();  
    }  

     class Consumer extends Thread{  

            @Override  
            public void run() {  
                consume();  
            }  

            private void consume() {         
                    try {  
                           lock.lock();  
                        System.out.println("我在等一个新信号"+this.currentThread().getName());  
                        condition.await();  

                    } catch (InterruptedException e) {  
                        // TODO Auto-generated catch block  
                        e.printStackTrace();  
                    } finally{  
                        System.out.println("拿到一个信号"+this.currentThread().getName());  
                        lock.unlock();  
                    }  

            }  
        }  

     class Producer extends Thread{  

            @Override  
            public void run() {  
                produce();  
            }  

            private void produce() {                   
                    try {  
                           lock.lock();  
                           System.out.println("我拿到锁"+this.currentThread().getName());  
                           condition.signalAll();                             
                           System.out.println("我发出了一个信号:"+this.currentThread().getName());  
                    } finally{  
                        lock.unlock();  
                    }  
                }  
     }  

}  

参考:www.jianshu.com/p/0a768e3b3…

觉得有收获的话帮忙点个赞吧,让有用的知识分享给更多的人

## 欢迎关注掘金号:五点半社

## 关注微信公众号:五点半社(工薪族的财商启蒙)##