java常见锁Reentrantlock,synchronized,SpinLock,ReadWriteLock

简介: 公平锁―是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后中请的线程比先中请的线程优先获取锁。在高并发的情况下,有可能会造成优先级反转或者饥饿现象

java公平锁和非公平锁(Reentrantlock)

  • 公平锁―是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
  • 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后中请的线程比先中请的线程优先获取锁。在高并发的情况下,有可能会造成优先级反转或者饥饿现象

并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是非公平锁。

此类的构造函数接受可选的公平性参数。当设置为true时,在争用下,锁有利于向等待时间最长的线程授予访问权限。否则,此锁不保证任何特定的访问顺序。与使用默认设置的程序相比,使用由许多线程访问的公平锁的程序可能显示出较低的总体吞吐量(即,较慢;通常要慢得多),但是在获得锁和保证没有饥饿的时间上差异较小。

但是请注意,锁的公平性并不能保证线程调度的公平性。因此,使用公平锁的多个线程中的一个线程可以连续多次获得公平锁,而其他活动线程则没有进行并且当前没有持有该锁。还要注意,不计时的 tryLock()方法不支持公平性设置。如果锁可用,即使其他线程正在等待,它也会成功。

两者区别

关于两者区别:

公平锁
Threads acquire a fair lock in the order in which they requested it.
公平锁就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。

非公平锁
a nonfair lock permits barging: threads requesting a lock can jump ahead of the queue of waiting threads if the lockhappens to be available when it is requested.
非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。

Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。

非公平锁的优点在于吞吐量比公平锁大。

对于Synchronized而言,也是一种非公平锁

值得注意的是:jvm对线程的调度策略是抢占式的,优先级越高的线程,可能越会获取cpu的处理权

java可重入锁和递归锁(Reentrantlock/synchronized)

可重入锁(也叫做递归锁)

指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁(也即线程对于内存锁代码,不需要再次争抢锁)。

也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。

ReentrantLock/synchronized就是一个典型的可重入锁。

可重入锁最大的作用是避免死锁。

synchronized可重入锁

class Phone {

    public synchronized void sendSMS() throws Exception{
        System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");

        // 在同步方法中,调用另外一个同步方法
        sendEmail();
    }


    public synchronized void sendEmail() throws Exception{
        System.out.println(Thread.currentThread().getId() + "\t invoked sendEmail()");
    }
}


public static void main(String[] args) {

        Phone phone = new Phone();

        new Thread(()->{
            phone.sendSMS();
        },"t1").start();

        new Thread(()->{
            phone.sendSMS();
        },"t2").start();
    }
AI 代码解读

输出结果

t1     invoked sendSMS()
11     invoked sendEmail()
t2     invoked sendSMS()
12     invoked sendEmail()
AI 代码解读

ReentrantLock可重入锁

public class ReentrantLockTest {
    public static void sendSMS(){
        System.out.println(Thread.currentThread().getName()+" sendSMS");
        sendEmail();
    }

    public static void sendEmail(){
        System.out.println(Thread.currentThread().getName()+" sendEmail");
    }


    public static void main(String[] args) {


        Lock lock = new ReentrantLock();

        new Thread(()->{
            lock.lock();
            try {
                sendSMS();
            }finally {
                lock.unlock();
            }
        },"t1").start();

        new Thread(()->{
            lock.lock();
            try {
                sendSMS();
            }finally {
                lock.unlock();
            }
        },"t2").start();

    }
}
AI 代码解读

输出结果

t1     invoked sendSMS()
11     invoked sendEmail()
t2     invoked sendSMS()
12     invoked sendEmail()
AI 代码解读

java自旋锁(spinLock)

自旋锁(Spin Lock)

是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU (以循环方式消耗cpu的代价,不让线程堵塞(挂起))

unsafe中的getAndAddInt就是一个良好示例

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
AI 代码解读
提到了互斥同步对性能最大的影响阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态完成,这些操作给系统的并发性能带来了很大的压力。同时,虚拟机的开发团队也注意到在许多应用上, 共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程 “稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。
《深入理解JVM.2nd》Page 398

为什么要使用自旋锁,就是多个线程对同一变量进行访问,为了线程安全加锁,但是由于线程使用共享变量很短一段时间,挂起线程进入堵塞状态,然后回复,消耗大。因此采用循环访问锁的方式,获取锁。

public class SpinLockTest {

    AtomicReference<String> atomicReference = new AtomicReference<>("unlock");

    public void lock(){
        System.out.println(Thread.currentThread().getName()+ " come in");

        // 如果值不为unlock时, 进行当前线程进行循环获取锁。值为unlock时,获取该锁并退出
        while (!atomicReference.compareAndSet("unlock","lock")){

        }

        System.out.println(Thread.currentThread().getName()+ " lock");

    }

    public void unlock(){
        atomicReference.compareAndSet("lock","unlock");
        System.out.println(Thread.currentThread().getName()+ " unlock");
    }


    public static void main(String[] args) {
        // 假设我们希望当原子引用共享对象为unlock值时,才可以访问这个共享对象
        SpinLockTest spinLock = new SpinLockTest();

        new Thread(()->{
            spinLock.lock();
            try {
                TimeUnit.SECONDS.sleep(8); }catch (InterruptedException e){
            }finally {
                spinLock.unlock();
            }
        },"t1").start();

        try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){}

        new Thread(()->{
            spinLock.lock();
            try {

            }finally {
                spinLock.unlock();
            }
        },"t2").start();

    }
}
AI 代码解读

java读写锁(ReentrantReadWriteLock)

独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁

共享锁:指该锁可被多个线程所持有。

多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。但是,如果有一个线程想去写共享资源来,就不应该再有其它线程可以对该资源进行读或写。

对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。

读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。

[读写锁详解
](https://blog.csdn.net/yucaixiang/article/details/89311527?utm_medium=distribute.pc_relevant.none-task-blog-2~default~OPENSEARCH~default-9.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~OPENSEARCH~default-9.control)

目录
打赏
0
0
0
0
5
分享
相关文章
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
锁状态bits1bit是否是偏向锁2bit锁标志位无锁状态对象的hashCode001偏向锁线程ID101轻量级锁指向栈中锁记录的指针000重量级锁指向互斥量的指针010尼恩提示,讲完 如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等优化手段 , 可以得到 120分了。如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等‌。JVM锁的膨胀、锁的内存结构变化相关的面试题,是非常常见的面试题。也是核心面试题。
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
87 3
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
本文介绍了几种常见的锁机制,包括公平锁与非公平锁、可重入锁与不可重入锁、自旋锁以及读写锁和互斥锁。公平锁按申请顺序分配锁,而非公平锁允许插队。可重入锁允许线程多次获取同一锁,避免死锁。自旋锁通过循环尝试获取锁,减少上下文切换开销。读写锁区分读锁和写锁,提高并发性能。文章还提供了相关代码示例,帮助理解这些锁的实现和使用场景。
124 4
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
|
4月前
|
Java 中的锁是什么意思,有哪些分类?
在Java多线程编程中,锁用于控制多个线程对共享资源的访问,确保数据一致性和正确性。本文探讨锁的概念、作用及分类,包括乐观锁与悲观锁、自旋锁与适应性自旋锁、公平锁与非公平锁、可重入锁和读写锁,同时提供使用锁时的注意事项,帮助开发者提高程序性能和稳定性。
181 3
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
68 4
|
5月前
|
Java 中锁的主要类型
【10月更文挑战第10天】
java源码 - ReentrantLock之FairSync
开篇  这篇文章主要是讲解FairSync公平锁的源码分析,整个内容分为加锁过程、解锁过程,CLH队列等概念。  首先一直困扰我的CLH队列的CLH的缩写我终于明白,看似三个人的人名的首字符缩写"CLH" (Craig, Landin, andHagersten)。
1044 0
java源码 - ReentrantLock之NonfairSync
开篇  NonfairSync和FairSync相比而言,多了一次抢占机会,其他处理逻辑几乎是一模一样。 NonfairSync的tryAcquire的操作流程中如果发现当前锁未被占用那么立即抢占锁。
1096 0
|
10天前
|
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
124 60
【Java并发】【线程池】带你从0-1入门线程池

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等