0

0

盘点Java中的各种锁

爱喝马黛茶的安东尼

爱喝马黛茶的安东尼

发布时间:2019-08-14 17:30:53

|

2903人浏览过

|

来源于blog

转载

盘点Java中的各种锁

锁的分类介绍

乐观锁与悲观锁

锁的一种宏观分类是乐观锁与悲观锁。乐观锁与悲观锁并不是特定的指哪个锁(Java 中也没有那个具体锁的实现名就叫乐观锁或悲观锁),而是在并发情况下两种不同的策略。

乐观锁(Optimistic Lock)就是很乐观,每次去拿数据的时候都认为别人不会修改。所以不会上锁。但是如果想要更新数据,则会在更新之前检查在读取至更新这段时间别人有没有修改过这个数据。如果修改过,则重新读取,再次尝试更新,循环上述步骤直到更新成功(当然也允许更新失败的线程放弃更新操作)。

立即学习Java免费学习笔记(深入)”;

悲观锁(Pessimistic Lock)就是很悲观,每次去拿数据的时候都认为别人会修改。所以每次都在拿数据的时候上锁。

这样别人拿数据的时候就会被挡住,直到悲观锁释放,想获取数据的线程再去获取锁,然后再获取数据。

悲观锁阻塞事务,乐观锁回滚重试,它们个有优缺点,没有好坏之分,只有适应场景的不同区别。比如:乐观锁适合用于写比较少的情况下,即冲突真的很少发生的场景,这样可以省去锁的开销,加大了系统的整个吞吐量。但是如果经常产生冲突,上层应用会不断的进行重试,这样反而降低了性能,所以这种场景悲观锁比较合适。

总结:乐观锁适合写比较少,冲突很少发生的场景;而写多,冲突多的场景适合使用悲观锁。

乐观锁的基础 --- CAS

在乐观锁的实现中,我们必须要了解的一个概念:CAS。

什么是 CAS 呢? Compare-and-Swap,即比较并替换,或者比较并设置。

比较:读取到一个值 A,在将其更新为 B 之前,检查原值是否为 A(未被其它线程修改过,这里忽略 ABA 问题)。

替换:如果是,更新 A 为 B,结束。如果不是,则不会更新。

上面两个步骤都是原子操作,可以理解为瞬间完成,在 CPU 看来就是一步操作。

有了 CAS,就可以实现一个乐观锁:

public class OptimisticLockSample{
    
    public void test(){
        int data = 123; // 共享数据
        
        // 更新数据的线程会进行如下操作
        for (;;) {
            int oldData = data;
            int newData = doSomething(oldData);
            
            // 下面是模拟 CAS 更新操作,尝试更新 data 的值
            if (data == oldData) { // compare
                data = newData; // swap
                break; // finish
            } else {
                // 什么都不敢,循环重试
            }
        }   
    }
    
    /**
    * 
    * 很明显,test() 里面的代码根本不是原子性的,只是展示了下 CAS 的流程。
    * 因为真正的 CAS 利用了 CPU 指令。
    *  
    * */ 
    
}

在 Java 中也是通过 native 方法实现的 CAS。

public final class Unsafe {
    
    ...
    
    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object 
    var5);
    
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
    
    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);  
    
    ...
}

上面写了一个简单直观的乐观锁(确切的来说应该是乐观锁流程)的实现,它允许多个线程同时读取(因为根本没有加锁操作),如果更新数据的话,

有且仅有一个线程可以成功更新数据,并导致其它线程需要回滚重试。CAS 利用 CPU 指令,从硬件层面保证了原子性,以达到类似于锁的效果。

从乐观锁的整个流程中可以看出,并没有加锁和解锁的操作,因此乐观锁策略也被称作为无锁编程。换句话说,乐观锁其实不是"锁",

它仅仅是一个循环重试的 CAS 算法而已。

相关推荐:《java开发教程

自旋锁

synchronized 与 Lock interface

Java 中两种实现加锁的方式:一种是使用 synchronized 关键字,另一种是使用 Lock 接口的实现类。

在一篇文章中看到一个好的对比,非常形象,synchronized 关键字就像是自动挡,可以满足一切的驾驶需求。

但是如果你想要做更高级的操作,比如玩漂移或者各种高级的骚操作,那么就需要手动挡,也就是 Lock 接口的实现类。

而 synchronized 在经过 Java 每个版本的各种优化后,效率也变得很高了。只是使用起来没有 Lock 接口的实现类那么方便。

synchronized 锁升级过程就是其优化的核心:偏向锁 -> 轻量级锁 -> 重量级锁

class Test{
    private static final Object object = new Object(); 
    
    public void test(){
        synchronized(object) {
            // do something        
        }   
    }
    
}

使用 synchronized 关键字锁住某个代码块的时候,一开始锁对象(就是上述代码中的 object)并不是重量级锁,而是偏向锁。

偏向锁的字面意思就是"偏向于第一个获取它的线程"的锁。线程执行完同步代码块之后,并不会主动释放偏向锁。当第二次到达同步代码块时,线程会判断此时持有锁的线程是否就是自己(持有锁的线程 ID 在对象头里存储),如果是则正常往下执行。由于之前没有释放,这里就不需要重新加锁,如果从头到尾都是一个线程在使用锁,很明显偏向锁几乎没有额外开销,性能极高。

一旦有第二个线程加入锁竞争,偏向锁转换为轻量级锁(自旋锁)。锁竞争:如果多个线程轮流获取一个锁,但是每次获取的时候都很顺利,没有发生阻塞,那么就不存在锁竞争。只有当某线程获取锁的时候,发现锁已经被占用,需要等待其释放,则说明发生了锁竞争。

在轻量级锁状态上继续锁竞争,没有抢到锁的线程进行自旋操作,即在一个循环中不停判断是否可以获取锁。获取锁的操作,就是通过 CAS 操作修改对象头里的锁标志位。先比较当前锁标志位是否为释放状态,如果是,将其设置为锁定状态,比较并设置是原子性操作,这个是 JVM 层面保证的。当前线程就算持有了锁,然后线程将当前锁的持有者信息改为自己。

假如我们获取到锁的线程操作时间很长,比如会进行复杂的计算,数据量很大的网络传输等;那么其它等待锁的线程就会进入长时间的自旋操作,这个过程是非常耗资源的。其实这时候相当于只有一个线程在有效地工作,其它的线程什么都干不了,在白白地消耗 CPU,这种现象叫做忙等。(busy-waiting)。所以如果多个线程使用独占锁,但是没有发生锁竞争,或者发生了很轻微的锁竞争,那么synchronized 就是轻量级锁,允许短时间的忙等现象。这是一种择中的想法,短时间的忙等,换取线程在用户态和内核态之间切换的开销。

显然,忙等是有限度的(JVM 有一个计数器记录自旋次数,默认允许循环 10 次,可以通过虚拟机参数更改)。如果锁竞争情况严重,

达到某个最大自旋次数的线程,会将轻量级锁升级为重量级锁(依然是通过 CAS 修改锁标志位,但不修改持有锁的线程 ID)。当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是上面说的忙等,即不会自旋),等待释放锁的线程去唤醒。在 JDK1.6 之前, synchronized直接加重量级锁,很明显现在通过一系列的优化过后,性能明显得到了提升。

JVM 中,synchronized 锁只能按照偏向锁、轻量级锁、重量级锁的顺序逐渐升级(也有把这个称为锁膨胀的过程),不允许降级。

可重入锁(递归锁)

可重入锁的字面意思是"可以重新进入的锁",即允许同一个线程多次获取同一把锁。比如一个递归函数里有加锁操作,递归函数里这个锁会阻塞自己么?

如果不会,那么这个锁就叫可重入锁(因为这个原因可重入锁也叫做递归锁)。

Java 中以 Reentrant 开头命名的锁都是可重入锁,而且 JDK 提供的所有现成 Lock 的实现类,包括 synchronized 关键字锁都是可重入的。

如果真的需要不可重入锁,那么就需要自己去实现了,获取去网上搜索一下,有很多,自己实现起来也很简单。

MediPro企业网站管理系统
MediPro企业网站管理系统

一款基于PHP+MYSQL开发的企业网站管理软件,具有灵活的栏目内容管理功能和丰富的网站模版,可用于创建各种企业网站。v5.1版本支持了PHP5+MYSQL5环境,前台网站插件开放源码,更利于个性化的网站开发。具有以下功能特点和优越性:[>]模版精美实用具有百款适合企业网站的精美模版,并在不断增加中[>]多语言支持独立语言包,支持GBK,UTF8编码方式,可用于创建各种语言的网站[&g

下载

如果不是可重入锁,在递归函数中就会造成死锁,所以 Java 中的锁基本都是可重入锁,不可重入锁的意义不是很大,我暂时没有想到什么场景下会用到;

注意:有想到需要不可重入锁场景的小伙伴们可以留言一起探讨。

下图展示一下 Lock 的相关实现类:

1565773894(1).png

公平锁和非公平锁

如果多个线程申请一把公平锁,那么获得锁的线程释放锁的时候,先申请的先得到,很公平。如果是非公平锁,后申请的线程可能先获得锁,是随机获取还是其它方式,都是根据实现算法而定的。

对 ReentrantLock 类来说,通过构造函数可以指定该锁是否是公平锁,默认是非公平锁。因为在大多数情况下,非公平锁的吞吐量比公平锁的大,如果没有特殊要求,优先考虑使用非公平锁。

而对于 synchronized 锁而言,它只能是一种非公平锁,没有任何方式使其变成公平锁。这也是 ReentrantLock 相对于 synchronized 锁的一个优点,更加的灵活。

以下是 ReentrantLock 构造器代码:

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock 内部实现了 FairSync 和 NonfairSync 两个内部类来实现公平锁和非公平锁。

可中断锁

字面意思是"可以响应中断的锁"。

首先,我们需要理解的是什么是中断。 Java 中并没有提供任何可以直接中断线程的方法,只提供了中断机制。那么何为中断机制呢?

线程 A 向线程 B 发出"请你停止运行"的请求,就是调用 Thread.interrupt() 的方法(当然线程 B 本身也可以给自己发送中断请求,

即 Thread.currentThread().interrupt()),但线程 B 并不会立即停止运行,而是自行选择在合适的时间点以自己的方式响应中断,也可以直接忽略此中断。也就是说,Java 的中断不能直接终止线程,只是设置了状态为响应中断的状态,需要被中断的线程自己决定怎么处理。这就像在读书的时候,老师在晚自习时叫学生自己复习功课,但学生是否复习功课,怎么复习功课则完全取决于学生自己。

回到锁的分析上来,如果线程 A 持有锁,线程 B 等待持获取该锁。由于线程 A 持有锁的时间过长,线程 B 不想继续等了,我们可以让线程 B 中断。

自己或者在别的线程里面中断 B,这种就是 可中段锁。

在 Java 中, synchronized 锁是不可中断锁,而 Lock 的实现类都是 可中断锁。从而可以看出 JDK 自己实现的 Lock 锁更加的灵活,这也就是有了 synchronized 锁后,为什么还要实现那么些 Lock 的实现类。

Lock 接口的相关定义:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
    void unlock();
    Condition newCondition();
}

其中 lockInterruptibly 就是获取可中断锁。

共享锁

字面意思是多个线程可以共享一个锁。一般用共享锁都是在读数据的时候,比如我们可以允许 10 个线程同时读取一份共享数据,这时候我们可以设置一个有 10 个凭证的共享锁。

在 Java 中,也有具体的共享锁实现类,比如 Semaphore。 

互斥锁

字面意思是线程之间互相排斥的锁,也就是表明锁只能被一个线程拥有。

在 Java 中, ReentrantLock、synchronized 锁都是互斥锁。

读写锁

读写锁其实是一对锁,一个读锁(共享锁)和一个写锁(互斥锁、排他锁)。

在 Java 中, ReadWriteLock 接口只规定了两个方法,一个返回读锁,一个返回写锁。

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();
    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

文章前面讲过[乐观锁策略](#乐观锁的基础 --- CAS),所有线程可以随时读,仅在写之前判断值有没有被更改。

读写锁其实做的事情是一样的,但是策略稍有不同。很多情况下,线程知道自己读取数据后,是否是为了更改它。那么为何不在加锁的时候直接明确。

这一点呢?如果我读取值是为了更新它(SQL 的 for update 就是这个意思),那么加锁的时候直接加写锁,我持有写锁的时候,别的线程。

无论是读还是写都需要等待;如果读取数据仅仅是为了前端展示,那么加锁时就明确加一个读锁,其它线程如果也要加读锁,不需要等待,可以直接获取(读锁计数器加 1)。

虽然读写锁感觉与乐观锁有点像,但是读写锁是悲观锁策略。因为读写锁并没有在更新前判断值有没有被修改过,而是在加锁前决定应该用读锁还是写锁。乐观锁特指无锁编程。

JDK 内部提供了一个唯一一个 ReadWriteLock 接口实现类是 ReentrantReadWriteLock。通过名字可以看到该锁提供了读写锁,并且也是可重入锁。

总结

Java 中使用的各种锁基本都是悲观锁,那么 Java 中有乐观锁么?结果是肯定的,那就是 java.util.concurrent.atomic 下面的原子类都是通过乐观锁实现的。如下:

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

通过上述源码可以发现,在一个循环里面不断 CAS,直到成功为止。

参数介绍

-XX:-UseBiasedLocking=false 关闭偏向锁
JDK1.6 
-XX:+UseSpinning 开启自旋锁
-XX:PreBlockSpin=10 设置自旋次数 
JDK1.7 之后 去掉此参数,由 JVM 控制

本文转自:https://blog.tommyyang.cn/2019/08/13/%E5%8F%B2%E4%B8%8A%E6%9C%80%E5%85%A8-Java-%E4%B8%AD%E5%90%84%E7%A7%8D%E9%94%81%E7%9A%84%E4%BB%8B%E7%BB%8D-2019/

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

2

2026.01.27

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

104

2026.01.26

edge浏览器怎样设置主页 edge浏览器自定义设置教程
edge浏览器怎样设置主页 edge浏览器自定义设置教程

在Edge浏览器中设置主页,请依次点击右上角“...”图标 > 设置 > 开始、主页和新建标签页。在“Microsoft Edge 启动时”选择“打开以下页面”,点击“添加新页面”并输入网址。若要使用主页按钮,需在“外观”设置中开启“显示主页按钮”并设定网址。

12

2026.01.26

苹果官方查询网站 苹果手机正品激活查询入口
苹果官方查询网站 苹果手机正品激活查询入口

苹果官方查询网站主要通过 checkcoverage.apple.com/cn/zh/ 进行,可用于查询序列号(SN)对应的保修状态、激活日期及技术支持服务。此外,查找丢失设备请使用 iCloud.com/find,购买信息与物流可访问 Apple (中国大陆) 订单状态页面。

93

2026.01.26

npd人格什么意思 npd人格有什么特征
npd人格什么意思 npd人格有什么特征

NPD(Narcissistic Personality Disorder)即自恋型人格障碍,是一种心理健康问题,特点是极度夸大自我重要性、需要过度赞美与关注,同时极度缺乏共情能力,背后常掩藏着低自尊和不安全感,影响人际关系、工作和生活,通常在青少年时期开始显现,需由专业人士诊断。

5

2026.01.26

windows安全中心怎么关闭 windows安全中心怎么执行操作
windows安全中心怎么关闭 windows安全中心怎么执行操作

关闭Windows安全中心(Windows Defender)可通过系统设置暂时关闭,或使用组策略/注册表永久关闭。最简单的方法是:进入设置 > 隐私和安全性 > Windows安全中心 > 病毒和威胁防护 > 管理设置,将实时保护等选项关闭。

6

2026.01.26

2026年春运抢票攻略大全 春运抢票攻略教你三招手【技巧】
2026年春运抢票攻略大全 春运抢票攻略教你三招手【技巧】

铁路12306提供起售时间查询、起售提醒、购票预填、候补购票及误购限时免费退票五项服务,并强调官方渠道唯一性与信息安全。

96

2026.01.26

个人所得税税率表2026 个人所得税率最新税率表
个人所得税税率表2026 个人所得税率最新税率表

以工资薪金所得为例,应纳税额 = 应纳税所得额 × 税率 - 速算扣除数。应纳税所得额 = 月度收入 - 5000 元 - 专项扣除 - 专项附加扣除 - 依法确定的其他扣除。假设某员工月工资 10000 元,专项扣除 1000 元,专项附加扣除 2000 元,当月应纳税所得额为 10000 - 5000 - 1000 - 2000 = 2000 元,对应税率为 3%,速算扣除数为 0,则当月应纳税额为 2000×3% = 60 元。

27

2026.01.26

oppo云服务官网登录入口 oppo云服务登录手机版
oppo云服务官网登录入口 oppo云服务登录手机版

oppo云服务https://cloud.oppo.com/可以在云端安全存储您的照片、视频、联系人、便签等重要数据。当您的手机数据意外丢失或者需要更换手机时,可以随时将这些存储在云端的数据快速恢复到手机中。

75

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.9万人学习

C# 教程
C# 教程

共94课时 | 7.7万人学习

Java 教程
Java 教程

共578课时 | 51.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号