Java中synchronized 和 Lock
1. synchronized 关键字
工作原理
- 对象锁:在Java中,每个对象都有一个与之关联的监视器锁(monitor lock)。当一个线程尝试进入由
synchronized保护的代码块或方法时,它必须首先获取该对象的监视器锁。如果锁已经被其他线程持有,则该线程将被阻塞,直到锁被释放。 - 类锁:对于静态同步方法来说,锁定的是这个类对应的Class对象,而不是实例对象。这意味着所有实例共享同一把锁。
内部机制
- 对象头与Mark Word:
- 每个Java对象都有一个对象头,其中包含了Mark Word。根据不同的锁状态(无锁、偏向锁、轻量级锁、重量级锁),Mark Word的内容会发生变化。
- 无锁状态:包含对象的哈希值或GC年龄。
- 偏向锁状态:适用于单线程访问场景,减少不必要的CAS操作。偏向锁会在对象头中记录偏向的线程ID。如果后续请求依旧来自同一线程,则无需再次获取锁。
- 轻量级锁状态:采用基于CAS的操作来尝试获取锁,避免直接进入重量级锁带来的性能损耗。轻量级锁通过在当前线程的栈帧中创建一个锁记录,并使用CAS操作尝试更新对象头中的Mark Word来实现。
- 重量级锁状态:如果竞争加剧,JVM会将锁膨胀为重量级锁。此时涉及到操作系统层面的调度,线程会被挂起等待锁的释放。
- 每个Java对象都有一个对象头,其中包含了Mark Word。根据不同的锁状态(无锁、偏向锁、轻量级锁、重量级锁),Mark Word的内容会发生变化。
- 锁升级过程:包括偏向锁、轻量级锁到重量级锁的转换过程,旨在减少线程获取锁的成本。锁升级的过程是自动的,不需要开发者干预。偏向锁 -> 轻量级锁 -> 重量级锁的升级路径是为了优化性能,在低竞争条件下尽可能地减少开销。
特性
- 自动管理:进入同步代码块或方法时自动获取锁,退出时自动释放锁,不需要手动操作。
- 不可中断:一旦一个线程开始等待获取
synchronized锁,则无法被中断,直到成功获得锁或当前线程结束。 - 公平性:不保证获取锁的顺序,可能会导致某些线程长时间等待(饥饿现象)。
- 支持重入:同一个线程可以多次获取同一个锁而不会造成死锁。这是通过计数器机制实现的,每次获取锁时计数器加一,释放锁时减一,当计数器归零时才真正释放锁。
- 可见性和有序性:提供内存屏障功能,确保了一个线程对共享变量所做的修改,在其释放锁之后,能够立即对随后获取同一把锁的其他线程可见,并防止指令重排序。
示例代码
public class SynchronizedExample {private int count = 0;// 同步方法public synchronized void increment() {count++;}// 同步代码块public void incrementWithBlock() {synchronized (this) {count++;}}// 静态同步方法public static synchronized void staticIncrement(SynchronizedExample instance) {instance.count++;}// 使用双重检查锁定模式初始化单例private static volatile SynchronizedExample singletonInstance;//双重检查锁定模式是一种高效的单例实现方式,它结合了懒加载、线程安全和性能优化的优点。通过 //volatile 和双重检查机制,可以确保在多线程环境下只创建一个实例,同时避免不必要的同步开销。public static SynchronizedExample getInstance() {if (singletonInstance == null) {synchronized (SynchronizedExample.class) {if (singletonInstance == null) {singletonInstance = new SynchronizedExample();}}}return singletonInstance;}
}
双重检查锁定模式详解
为什么需要双重检查锁定?
- 性能优化:如果直接使用
synchronized方法或代码块进行单例初始化,每次访问实例时都会进入同步代码块,这会导致性能开销较大。 - 懒加载:只有在第一次访问实例时才进行初始化,而不是在类加载时就创建实例。
- 线程安全:确保多个线程同时访问时不会创建多个实例。
双重检查锁定的核心思想是:
- 在第一次检查
singletonInstance == null时,避免进入同步块。 - 在第二次检查
singletonInstance == null时,确保只有一个线程能够创建实例。
实现步骤
- 第一次检查:在获取锁之前,先检查实例是否已经存在。如果已经存在,则直接返回实例,避免进入同步块。
- 加锁:如果实例尚未创建,则进入同步块。
- 第二次检查:在同步块内再次检查实例是否已经存在。这是为了防止多个线程同时通过第一次检查后都尝试创建实例。
- 创建实例:如果实例仍然为
null,则创建实例并赋值给静态变量。
代码示例
以下是完整的双重检查锁定模式实现单例的示例代码:
public class Singleton {// 使用 volatile 确保实例的可见性和防止指令重排序private static volatile Singleton instance;// 私有构造函数,防止外部实例化private Singleton() {// 防止反射攻击(可选)if (instance != null) {throw new RuntimeException("Use getInstance() method to get the single instance.");}}// 提供全局访问点public static Singleton getInstance() {// 第一次检查:避免不必要的同步if (instance == null) {synchronized (Singleton.class) {// 第二次检查:确保只有一个线程能创建实例if (instance == null) {instance = new Singleton();}}}return instance;}// 测试方法public void showMessage() {System.out.println("Hello from Singleton instance!");}public static void main(String[] args) {// 多线程测试Runnable task = () -> {Singleton singleton = Singleton.getInstance();singleton.showMessage();};Thread t1 = new Thread(task);Thread t2 = new Thread(task);t1.start();t2.start();}
}
代码解析
-
volatile关键字的作用:- 确保
instance的可见性:当一个线程修改了instance的值,其他线程能够立即看到最新的值。 - 防止指令重排序:在 JVM 中,对象的创建可能被分解为以下步骤:
- 分配内存空间。
- 初始化对象。
- 将引用指向分配的内存地址。 如果没有
volatile,JVM 可能会因为指令重排序导致其他线程看到一个未完全初始化的对象。
- 确保
-
双重检查的意义:
- 第一次检查:减少同步的开销。如果实例已经存在,直接返回,避免进入同步块。
- 第二次检查:确保只有一个线程能够创建实例,即使多个线程同时通过了第一次检查。
-
私有构造函数:
- 防止外部通过
new Singleton()创建新的实例。 - 可以进一步增强安全性,防止反射攻击。
- 防止外部通过
-
多线程测试:
- 通过两个线程同时调用
getInstance()方法,验证是否只有一个实例被创建。
- 通过两个线程同时调用
运行结果
运行上述代码时,输出结果类似如下(顺序可能不同):
Hello from Singleton instance!
Hello from Singleton instance!
尽管有两个线程同时调用 getInstance() 方法,但只会创建一个 Singleton 实例。
注意事项
-
volatile是必需的:- 如果不使用
volatile,可能会因为指令重排序导致其他线程看到未完全初始化的对象,从而引发问题。
- 如果不使用
-
反射攻击:
- 即使使用了私有构造函数,仍然可以通过反射机制强制调用构造函数创建新的实例。可以在构造函数中添加检查逻辑来防止这种情况。
-
序列化问题:
- 如果单例类实现了
Serializable接口,反序列化时可能会创建新的实例。可以通过定义readResolve()方法解决此问题:protected Object readResolve() {return instance; }
- 如果单例类实现了
2. 显式锁 (Lock 接口)
工作原理
- 显式调用:需要显式地调用
lock()方法来获取锁,并在finally块中调用unlock()方法来释放锁。 - 多种锁机制:提供了比
synchronized更多的锁机制,如可重入锁、读写锁等。
特性
- 灵活性高:可以通过编程控制何时锁定、解锁,允许尝试获取锁、定时获取锁等操作。
- 可中断:可以中断正在等待获取锁的线程,避免了潜在的阻塞问题。例如,
lockInterruptibly()方法允许你中断正在等待获取锁的线程。 - 公平性选择:可以选择是否启用公平锁(先请求的线程优先获得锁),有助于防止饥饿现象。例如,
new ReentrantLock(true)创建一个公平锁。 - 多种条件变量:支持多个条件变量(Condition),比
synchronized的单一条件通知机制更为灵活。例如,你可以使用newCondition()方法创建多个条件变量。 - 支持重入:同样支持重入,即同一个线程可以多次获取同一个锁。这也是通过计数器机制实现的。
示例代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockExample {private final Lock lock = new ReentrantLock();private final Condition condition = lock.newCondition();private int count = 0;// 使用显式锁进行同步public void increment() {lock.lock(); // 获取锁try {count++;} finally {lock.unlock(); // 确保在finally块中释放锁,避免死锁}}// 条件变量的使用public void awaitAndSignal() throws InterruptedException {lock.lock();try {System.out.println("Before await");condition.await(); // 当前线程等待System.out.println("After await");} catch (InterruptedException e) {Thread.currentThread().interrupt(); // 重新设置中断标志} finally {lock.unlock();}lock.lock();try {condition.signal(); // 唤醒等待的线程} finally {lock.unlock();}}// 尝试获取锁并设置超时时间public void incrementWithTimeout() throws InterruptedException {if (lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试获取锁,最多等待1秒try {count++;} finally {lock.unlock();}} else {System.out.println("Unable to acquire lock");}}// 可中断锁获取public void incrementInterruptibly() throws InterruptedException {lock.lockInterruptibly(); // 可以响应中断的锁获取try {count++;} finally {lock.unlock();}}// 使用公平锁public static void fairLockExample() {Lock fairLock = new ReentrantLock(true); // 创建一个公平锁// 使用fairLock进行同步操作}
}
实际应用中的最佳实践
- 简单同步需求:如果你只需要对方法或代码块进行简单的同步处理,
synchronized是一个不错的选择,因为它简单且不易出错。 - 复杂同步需求:如果你需要更多的控制,比如定时锁等待、可中断锁等待或公平锁等高级功能,那么
Lock接口将是更好的选择。 - 锁细化:尽量缩小同步区域范围,以减少线程之间的争用,提高并发效率。
- 双重检查锁定:在单例模式中常用,减少同步开销。双重检查锁定是一种优化技术,用于延迟初始化单例对象,减少同步开销。
- 使用显式锁:如
ReentrantLock,ReadWriteLock提供了比synchronized更灵活的锁机制。特别是ReadWriteLock可以允许多个线程同时读取数据,但只允许一个线程写入数据。 - 避免死锁:确保所有线程以相同的顺序获取锁,避免循环等待的情况发生。例如,定义一个全局的锁获取顺序,并严格遵守该顺序。
注意事项
- 性能对比:虽然
synchronized在很多情况下表现良好,但对于某些复杂的并发场景,显式锁(如ReentrantLock)可能提供更好的性能和更大的灵活性。 - 异常处理:在使用显式锁时,务必在finally块中释放锁,以防止因异常而导致死锁。
- 调试技巧:利用日志或调试工具监控锁的状态和线程的行为,可以帮助诊断并发问题。
相关文章:
Java中synchronized 和 Lock
1. synchronized 关键字 工作原理 对象锁:在Java中,每个对象都有一个与之关联的监视器锁(monitor lock)。当一个线程尝试进入由 synchronized 保护的代码块或方法时,它必须首先获取该对象的监视器锁。如果锁已经被其…...
Linux系统-ls命令
一、ls命令的定义 Linux ls命令(英文全拼:list directory contents)用于显示指定工作目录下之内容(列出目前工作目录所含的文件及子目录)。 二、ls命令的语法 ls [选项] [目录或文件名] ls [-alrtAFR] [name...] 三、参数[选项…...
个人学习编程(3-24) 数据结构
括号的匹配: if((s[i]) && now() || (s[i]] && now[)){ #include <bits/stdc.h>using namespace std;int main() {char s[300];scanf("%s",&s);int i;int len strlen(s);stack <char> st;for (i 0; i < len; i){if(…...
.NET开源的智能体相关项目推荐
一、AntSK 由AIDotNet团队开发的人工智能知识库与智能体框架,支持多模型集成和离线部署能力。 核心能力: • 支持OpenAI、Azure OpenAI、星火、阿里灵积等主流大模型,以及20余种国产数据库(如达梦) • 内置语义内核&a…...
面试八股文--框架篇(SSM)
一、Spring框架 1、什么是spring Spring框架是一个开源的Java平台应用程序框架,由Rod Johnson于2003年首次发布。它提供了一种全面的编程和配置模型,用于构建现代化的基于Java的企业应用程序。Spring框架的核心特性包括依赖注入(DI…...
跨语言语言模型预训练
摘要 最近的研究表明,生成式预训练在英语自然语言理解任务中表现出较高的效率。在本研究中,我们将这一方法扩展到多种语言,并展示跨语言预训练的有效性。我们提出了两种学习跨语言语言模型(XLM)的方法:一种…...
[识记]Mysql8 远程授权
今天在测试docker时,因更换为Mysql8,使用SQL方式实现远程授权,其方式方法同于Mysql,但语句稍有不同,仅供参考。 登录mysql mysql -u root -p 输入密码: [请依据交互输入你的mysql密码]切换数据库 use mysql;选择需要…...
Nodejs上传文件的问题
操作系统:window和linux都会遇到 软件环境:v20.10.0的Nodejs 1、前端代码如下: 2、后端Nodejs 2.1、注册接口 2.2、上传接口 其中memoryUpload方法代码如下: 3、用页面上传文件 查看具体报错原因: TypeError: sourc…...
无人机螺旋桨平衡标准
螺旋桨平衡是确保无人机(UAV)平稳运行、可靠性和使用寿命的关键过程。螺旋桨的不平衡会导致振动、噪音,并加速关键部件的磨损,从而对飞行性能产生负面影响。 ISO 21940-11:2016标准为旋翼平衡提供了一个广泛引用的框架,定义了可接受的不平衡…...
Qt开发:双缓冲机制
文章目录 什么是双缓冲机制?Qt 中的双缓冲实现方式 什么是双缓冲机制? 双缓冲(Double Buffering) 是一种减少屏幕绘制闪烁的技术,广泛用于图形渲染和游戏开发。 它的基本原理是: 先在后台缓冲区࿰…...
HandyJSON原理
HandyJSON 的优势 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式, 应用广泛. 在 App 的使用过程中, 服务端给移动端发送的大部分都是 JSON 数据, 移动端需要解析数据才能做进一步的处理. 在解析JSON数据这一块, 目前 Swift 中流行的框架基本上是 SwiftyJSON, …...
SpringBoot+策略模式+枚举类,使用配置文件改进,优雅消除if-else,完全符合OOP原则
需求分析 公司做物联网系统的,使用nettry进行设备连接,对设备进行数据采集,根据设备的协议对数据进行解析,解析完成之后存放数据库,但是不同厂家的设备协议不同。公司系统使用了使用了函数式编程的去写了一个解析类&am…...
[力扣每日一练]关于MySQL和pandas的正则表达式应用
一:题目要求 表:Users-------------------------- | Column Name | Type | -------------------------- | user_id | int | | email | varchar | -------------------------- (user_id) 是这张表的唯一主键。 每一行包含用…...
每日免费分享之精品wordpress主题系列~DAY16
主题介绍: 今日在网上寻找wordpress主题的时候逛到了大叔的网站,赶脚这个主题蛮不错的,于是百度一下,果然,这个主题很受欢迎。作为主题下载站追梦者也不甘落后,马上就发布出来了,希望对你们有用…...
OpenCV图像拼接(9)实现图像拼接功能的一个高级接口cv::Stitcher
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::Stitcher 类是OpenCV中用于实现图像拼接功能的一个高级接口。它简化了从一组部分重叠的图像创建全景图的过程,隐藏了许多底层细节…...
MySQL 用户权限与安全管理
MySQL 用户权限与安全管理 在数据库系统中,用户权限与安全管理是保障数据安全性、完整性和隐私性的核心机制。MySQL 提供了丰富的权限管理功能,可以精细地控制用户对数据库、表以及其他数据库对象的访问权限,同时也支持各种安全管理策略来防…...
dify + deepseek /qwen + win +xinference 等完成知识库建设
Dify.AI The Innovation Engine for Generative AI Applications 简介:Dify是一个用于构建人工智能应用程序的开源平台。我们将后端即服务和LLMOps相结合,简化了生成式人工智能解决方案的开发,使开发人员和非技术创新者都可以使用。CPU>…...
模数转换电路(A/D转换器)
A/D转换,是将输入的模拟电压量转换成相应的数字量。 A/D转换器的类型很多,按工作原理可分为直接转换型和间接转换型两大类。前者直接将模拟电压量转换成数字量,后者是先将模拟电压量转换成一个中间量,再将中间量转换成数字量。 …...
算法 | 麻雀搜索算法原理,公式,改进算法综述,应用场景及matlab完整代码
一、麻雀搜索算法(SSA)原理 1. 算法基础 麻雀搜索算法(Sparrow Search Algorithm, SSA)是2020年提出的一种群体智能优化算法,灵感来源于麻雀群体的觅食与反捕食行为。算法将麻雀分为三类角色:发现者(Producer):适应度最高,负责探索全局最优区域;加入者(Follower)…...
OpenAI深夜直播「偷袭」谷歌!GPT-4o原生图像生成:奥特曼带梗图,AGI战场再燃战火
引言:AI战场的「闪电战」 当谷歌刚刚发布「地表最强」Gemini 2.5 Pro时,OpenAI立即以一场深夜直播「闪电反击」——GPT-4o的原生图像生成功能正式上线!从自拍变梗图到相对论漫画,奥特曼(OpenAI团队)用一连…...
【深度学习】Cross-Attention(交叉注意力)机制详解与应用
Cross-Attention(交叉注意力)机制详解与应用 文章目录 Cross-Attention(交叉注意力)机制详解与应用引言什么是Cross-Attention?Cross-Attention的数学表示Cross-Attention与Self-Attention的区别Cross-Attention的应用…...
《大语言模型赋能证券业开发安全:海云安技术方案在上交所专刊发表》
近日,海云安《大语言模型在证券业开发安全领域的探索与实践》技术方案经过上海证券交易所(以下简称”上交所“)行业专家评审后正式收录于《交易技术前沿——网络安全专刊(2025年第1期 总第61期)》。 证券信息技术研究…...
光流 | 基于光流的人体异常行为检测算法原理,公式,算法改进,matlab代码
===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 人体异常行为检测 一、算法原理与数学模型1. 核心原理2. 关键公式二、算…...
【蓝桥杯】单片机设计与开发,中断系统,外部中断(下)
一、例程一:外部中断执行函数 #include<stc15.h>void main(void) {P20XA0;P00X00;P20X80;P00XFF;IT00;//设置外部中断0;上升沿下降沿均可//IT01;//设置外部中断0;仅下降沿EX01;//允许中断0申请中断EA 1;//打开CPU总中断while(1); }voi…...
【测试工具】如何使用 burp pro 自定义一个拦截器插件
在 Burp Suite 中,你可以使用 Burp Extender 编写自定义拦截器插件,以拦截并修改 HTTP 请求或响应。Burp Suite 支持 Java 和 Python (Jython) 作为扩展开发语言。以下是一个完整的流程,介绍如何创建一个 Burp 插件来拦截请求并进行自定义处理…...
MySQL、创建数据库、表、SQL 函数:数学函数、字符串函数、日期函数、聚合函数
DAY18.1 Java核心基础 MySQL 创建数据库 数据库是一个服务,实际开发需要根据具体的项目创建对应的数据库实例 create database mytest1 default character set utf8 collate utf8_general_ci; create database mytest2 default character set utf8 collate utf8…...
关于我对接了deepseek之后部署到本地将数据存储到mysql的过程
写在前面 今天写一下使用nodejs作为服务端,vue作为客户端,mysql的数据库,对接deepseek的全过程,要实现一个很简单的效果就是,可以自由的询问,然后可以将询问的过程存储到mysql的数据库中。 文档对接 deeps…...
23种设计模式-策略(Strategy)设计模式
策略设计模式 🚩什么是策略设计模式?🚩策略设计模式的特点🚩策略设计模式的结构🚩策略设计模式的优缺点🚩策略设计模式的Java实现🚩代码总结🚩总结 🚩什么是策略设计模式…...
【Exception】MybatisPlusException: can not find lambda cache for this entity
文章目录 环境 | Environment复现步骤 | Reproduction steps报错日志 | Error log源码 | Source CodeUserServiceImpl.javaAddressServiceImpl.javaAbstractSubTableBaseServiceImpl.javaUserEntity.javaAddressEntity.javaSubTableBaseEntity.java 原因分析 | Analysis解决方案…...
vue ts+Windi CSS
1、创建vue项目 trae(字节)打开一个空文件夹 npm install -g vue/cli vue create my-project cd my-project vue add typescript npm run serve vue项目创建完成 2、安装windicss vue add windicss vue.config.js配置 npm install vue-router …...
