Java 中各种锁

 2023-01-31
原文作者:蒋先森 原文地址:https://jlj98.top/

数据库中的各种锁可以前往数据库锁机制查看。Java中提供的各种锁可以实现并发编程。
锁是用来控制多个线程访问共享资源的方式,一辩来说,一个锁能够防止多个线程同时访问共享资源。

可重入锁

可重入锁 ,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。

在JAVA环境下 ReentrantLock 和 Synchronized 都是 可重入锁
可重入锁最大的作用是避免死锁。

自旋锁

自旋锁是采用让当前线程不停地在循环体内执行,当循环的条件被其他线程改变时才能进入临界区。

自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不断增加时,性能下降明显,因为每个线程都需要执行,会占用CPU时间片。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。

独享锁

独享锁是指该锁一次只能被一个线程所持有。ReentrantLock 、Synchronized 都是独享锁。

    Lock lock = new ReentrantLock();
    lock.lock();
    try {
                
    }catch (Exception e){
                
    }finally {
        lock.unlock();
    }

ReetrankLock 与 synchronized 比较

Java 并发编程之 Synchronized 详解

Java 并发编程之 ReentrantLock 详解

synchronized 在1.5版本之前性能低下,因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。但在之后的JDK版本中进行了大量的优化,性能已经和ReetrankLock相媲美。 使用 synchronized 关键字将会隐式的获取锁,但是它将锁的获取和释放固定化了,也就是先获取再释放。

Synchronized 底层实现原理:

  • Synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。
  • Synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

ReentrantLock与synchronized很相似,它们都具备一样的线程重入特性,只是代码写法上有点区别而已,一个表现为API层面的互斥锁(Lock),一个表现为原生语法层面的互斥锁(Synchronized)

共享锁

共享锁是指该锁可被多个线程所持有。ReentrantReadWriteLock,其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证并发读是非常高效的,读写、写读、写写的过程是互斥的。独享锁与共享锁也是通过AQS(AbstractQueuedSynchronizer)来实现的,通过实现不同的方法,来实现独享或者共享

互斥锁

独享锁/共享锁就是一种广义的说法,互斥锁/读写锁指具体的实现。互斥锁在Java中的具体实现就是ReentrantLock

读写锁

在Java中读写锁的具体实现是 ReentrantReadWriteLock,这个类在java.util.concurrent.locks包下面。

    private static volatile ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    public static void main(String[] args) {
        Object data = null;
        //上读锁,其他线程只能读不能写
        readWriteLock.readLock().lock();
        try {
            //do something
        }finally {
            readWriteLock.readLock().unlock();
        }
        //上写锁,不允许其他线程读也不允许写
        readWriteLock.writeLock().lock();
        try {
            //do something
        }finally {
            readWriteLock.writeLock().unlock();
        }
    
    }

下面这篇博客对读写锁分析的挺不错的。

公平锁和不公平锁

两者的区别主要体现在 是否按照申请锁的顺序来获取锁

Java多线程 – 公平锁和非公平锁

  • 公平锁:多个线程按照申请锁的顺序来获取锁。
  • 不公平锁:非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。可能造成优先级反转或者饥饿现象。对于Java ReentrantLock而言,通过构造函数 ReentrantLock(boolean fair) 指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。对于Synchronized而言,也是一种非公平锁。

Reference