运维.售后
论坛
潜水/灌水快乐,沉淀知识,认识更多同行。
ToB圈子
加入IT圈,遇到更多同好之人。
朋友圈
看朋友圈动态,了解ToB世界。
博客
Blog
ToB门户
了解全球最新的ToB事件
排行榜
Ranklist
文库
业界最专业的IT文库,上传资料也可以赚钱
下载
分享
Share
导读
Guide
相册
Album
记录
Doing
搜索
本版
文章
帖子
ToB圈子
用户
免费入驻
产品入驻
解决方案入驻
公司入驻
案例入驻
登录
·
注册
只需一步,快速开始
账号登录
立即注册
找回密码
用户名
Email
自动登录
找回密码
密码
登录
立即注册
首页
找靠谱产品
找解决方案
找靠谱公司
找案例
找对的人
专家智库
悬赏任务
圈子
SAAS
ToB企服应用市场:ToB评测及商务社交产业平台
»
论坛
›
软件与程序人生
›
后端开发
›
Java
›
深入浅出Java多线程(九):synchronized与锁
深入浅出Java多线程(九):synchronized与锁
怀念夏天
金牌会员
|
2024-4-25 10:05:39
|
显示全部楼层
|
阅读模式
楼主
主题
890
|
帖子
890
|
积分
2670
引言
大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第九篇内容:synchronized与锁。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!!
在现代软件开发中,多线程技术是提升系统性能和并发能力的关键手段之一。Java作为主流的编程语言,其内置的多线程机制为开发者提供了丰富的并发控制工具,其中synchronized关键字及其背后的锁机制扮演了至关重要的角色。理解并掌握synchronized的使用原理与特性,有助于我们设计出高效且线程安全的应用程序。
Java中的每个对象都可以充当一把锁,这意味着任何实例方法或静态方法可以通过synchronized关键字来实现同步控制,从而确保同一时间只有一个线程能访问临界资源。例如,一个简单的实例方法同步:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
}
复制代码
在这个例子中,increment方法被synchronized修饰,使得在同一时刻只能有一个线程对count变量进行递增操作,避免了数据竞争带来的不一致性问题。
同时,类锁的概念也是基于对象锁——类的Class对象同样可以作为锁,用于同步类的静态方法或某一特定对象实例上的代码块,如:
public class SharedResource {
public static synchronized void modifyStaticData() {
// 修改共享静态数据
}
}
复制代码
这里,modifyStaticData方法通过类锁保护了所有实例共享的静态资源,保证了在多线程环境下的数据安全性。
深入探究Java多线程中的synchronized关键字及锁机制,我们会发现Java虚拟机为了优化锁的性能,引入了偏向锁、轻量级锁和重量级锁等不同级别的锁状态,并且支持锁的自动升级和降级策略。这些机制能够根据实际的并发场景动态调整锁的表现形式,以最小化锁的获取和释放开销,进而提高系统的并发性能和响应速度。接下来,我们将逐一剖析这些概念和技术细节,以便更全面地理解和运用Java中的锁机制。
Java锁基础
在Java多线程编程中,锁机制是实现并发控制的核心手段之一。这里的“锁”基于对象的概念,任何Java对象都可以充当一把锁来保护共享资源的访问,确保同一时间只有一个线程可以执行临界区代码。synchronized关键字作为Java内置的关键同步工具,被广泛用于实现线程间的互斥操作。
synchronized关键字详解
synchronized关键字主要有三种使用形式:
实例方法锁定
:当synchronized关键字修饰实例方法时,它隐式地获取了当前对象实例作为锁:
public class SynchronizedExample {
private int counter;
public synchronized void increment() {
counter++;
}
}
复制代码
在上述代码中,increment方法被synchronized修饰,意味着每次仅有一个线程能执行该方法内部逻辑,即修改counter变量。
静态方法锁定
:如果synchronized修饰的是静态方法,则锁对象为类的Class对象,所有实例共享这把锁:
public class SynchronizedExample {
private static int sharedCounter;
public static synchronized void incrementStatic() {
sharedCounter++;
}
}
复制代码
在这个例子中,对incrementStatic方法的访问将受到类锁的保护,确保在多线程环境下,对sharedCounter的更新是原子性的。
代码块锁定
:通过synchronized关键字包裹一个代码块,显式指定锁对象:
public class SynchronizedExample {
private final Object lock = new Object();
public void blockLockingMethod() {
synchronized (lock) {
// 临界区代码
}
}
复制代码
在这里,我们创建了一个独立的对象lock用作锁,只有获得了这把锁的线程才能执行代码块内的内容。
synchronized关键字保证了其修饰的方法或代码块在同一时间只能由单个线程访问,从而避免了因多个线程同时修改数据导致的数据不一致问题,有效地实现了多线程环境下的同步控制。随着JVM对锁性能优化的不断深入,还引入了偏向锁、轻量级锁和重量级锁等不同级别的锁状态,使得Java多线程同步更加灵活高效。
synchronized原理
在Java多线程编程中,synchronized关键字所实现的同步机制深入底层,与JVM内部对象头结构密切相关。每个Java对象都拥有一个对象头(Object Header),它是内存中存放对象元数据的地方,包含了对象的Mark Word区域,这个区域用于存储对象的hashCode、GC分代年龄以及锁状态等信息。
Java对象头与锁状态
对象头结构
:非数组类型的Java对象,其对象头占用2个机器字宽,对于32位系统是32位,64位系统则是64位。Mark Word中的一部分空间被用来记录锁的状态,包括无锁、偏向锁、轻量级锁和重量级锁四种状态。
长度内容作用32/64bitMark Word存储对象的hashCode或锁信息等32/64bitClass Metadata Address存储到对象类型数据的指针32/64bitArray length数组的长度(如果是数组)这里着重关注一些Mark Word 的内容:
锁状态29bit或者61bit第1bit是否偏向锁第2bit锁标志位无锁001偏向锁线程ID101轻量级锁指向栈中锁记录的指针此时第1bit不用于标识偏向锁00重量级锁指向互斥量(重量级锁)的指针此时第1bit不用于标识偏向锁10
锁状态转换
:
无锁状态
:没有任何线程持有该对象锁,所有线程都可以尝试修改资源。
偏向锁
:当一个线程首次获得锁时,会将当前线程ID写入对象头的Mark Word中,后续进入同步代码块时只需检查是否为当前线程持有即可快速获取锁。例如,若只有一个线程长期访问某一对象,则可以避免不必要的CAS操作和自旋消耗。
class BiasedLockExample {
private int count;
public void increment() {
synchronized (this) {
count++;
}
}
}
复制代码
在上述例子中,如果increment方法仅由一个线程执行,那么JVM可能会将对象标记为偏向锁,从而提高效率。
轻量级锁
:当存在多个线程竞争同一锁,但实际发生锁竞争的概率较小的情况下,JVM使用轻量级锁来避免频繁的线程阻塞和唤醒开销。轻量级锁通过CAS操作试图将当前线程栈中的锁记录地址替换到对象头的Mark Word中,如失败则表明存在锁竞争,转而升级为自旋或重量级锁。
重量级锁
:当锁竞争激烈时,轻量级锁无法满足需求,就会升级为依赖于操作系统的互斥量(mutex)实现的重量级锁。此时线程将被挂起,直到锁释放后重新调度,降低了CPU的利用率但确保了线程间互斥性。
Java虚拟机通过对象头的Mark Word动态调整锁状态以适应不同场景下的并发控制需求,实现了从偏向锁、轻量级锁到重量级锁的平滑过渡,有效提升了多线程环境下程序的性能表现。通过灵活运用和理解这些锁状态及其背后的原理,开发者能够更好地优化多线程应用中的同步逻辑。
Java锁升级机制
在Java多线程同步中,synchronized关键字实现的锁具有动态升级的能力,从偏向锁到轻量级锁再到重量级锁,根据竞争情况自动调整以优化性能。
偏向锁
偏向锁
是为了解决大多数情况下只有一个线程频繁获得锁的情况。当一个线程首次获取对象锁时,JVM会将其设置为偏向锁,并将该线程ID记录在对象头的Mark Word中。后续该线程再次进入同步代码块时,只需简单地验证Mark Word中的线程ID是否与当前线程一致即可快速获取锁。例如:
public class BiasedLockExample {
private int sharedResource;
public void access() {
synchronized (this) {
// 仅有一个线程长期访问此方法时,偏向锁生效
sharedResource++;
}
}
}
复制代码
如果其他线程尝试获取已被偏向的锁,系统会检查偏向锁是否有效并进行撤销操作,通过CAS尝试替换Mark Word的内容。若失败,则表明存在锁竞争,此时偏向锁升级至轻量级锁。其操作流程如下图:
下图总结了偏向锁的获得和撤销流程:
轻量级锁
轻量级锁
主要应用于多个线程间交替访问同一对象但不存在大量持续竞争的场景。当线程试图获取锁时,它首先会在自己的栈帧中创建一个用于存储锁记录的空间(Displaced Mark Word),然后通过CAS操作尝试将对象头的Mark Word替换为指向锁记录的指针。成功则表示获得锁;否则,线程开始自旋(循环尝试获取锁)。
public class LightweightLockExample {
private int sharedResource;
public void access() {
Object lock = new Object();
synchronized (lock) {
// 若多个线程短暂交替访问此方法,轻量级锁生效
sharedResource++;
}
}
}
复制代码
自旋次数并非固定不变,而是采用了适应性自旋策略,即根据历史成功率动态调整自旋次数。如果经过若干次自旋后仍未能获得锁,则轻量级锁升级为重量级锁。轻量锁操作流程如下:
重量级锁
重量级锁
依赖于操作系统的互斥量(mutex)来实现线程间的互斥控制。当锁竞争激烈,轻量级锁无法满足需求时,锁状态会转换为重量级锁。这时,请求锁的线程会被挂起并放入等待队列中,直至持有锁的线程释放锁资源。
public class HeavyweightLockExample {
private static final Object lock = new Object();
public void concurrentAccess() {
synchronized (lock) {
// 若大量并发线程同时访问此方法,可能导致锁升级为重量级锁
// 线程将被操作系统调度器挂起和唤醒
performHeavyOperation();
}
}
private void performHeavyOperation() {
// 执行耗时较长的操作...
}
}
复制代码
重量级锁虽然会导致线程阻塞及上下文切换,但它确保了在高度竞争环境下的公平性和线程安全。当调用wait()或notify()方法时,即使原本是轻量级或偏向锁,也会先膨胀成重量级锁,以便正确管理线程的阻塞和唤醒状态。
总结来说,Java锁的升级机制是一种根据实际运行状况动态调整同步成本的技术手段,使得在多种并发场景下都能尽可能保持高效率和线程安全性。
锁对比与选择
在Java多线程同步中,有三种主要的锁类型:偏向锁、轻量级锁和重量级锁。每种锁都有其特定的适用场景及性能特性。
偏向锁
优点
:当只有一个线程长期独占对象锁时,偏向锁几乎无额外开销,获取和释放锁的速度接近非同步方法调用。
缺点
:当存在锁竞争或者程序执行过程中锁的所有者发生变化时,需要撤销偏向锁并升级为更高级别的锁,这个过程会产生额外的系统开销。
适用场景
:适用于大部分时间只由一个线程访问同步块的场合。
案例:
public class BiasedLockExample {
private int sharedResource;
public void exclusiveAccess() {
synchronized (this) {
// 若只有主线程频繁访问此方法,则偏向锁效率高
sharedResource++;
}
}
}
复制代码
轻量级锁
优点
:相比于重量级锁,轻量级锁通过自旋避免了线程上下文切换带来的开销,在没有其他线程竞争的情况下能快速获得锁,提高了程序响应速度。
缺点
:如果多个线程同时争夺锁,轻量级锁会导致较多的CAS操作以及可能的长时间自旋等待,反而浪费CPU资源。
适用场景
:适用于线程间对锁的竞争不激烈且锁持有时间较短的情况。
案例:
public class LightweightLockExample {
private final Object lock = new Object();
public void concurrentAccess() {
synchronized (lock) {
// 若并发线程交替短暂持有锁,轻量级锁效果好
processData();
}
}
private void processData() {
// 执行一些快速计算或短期持有的共享资源访问...
}
}
复制代码
重量级锁
优点
:确保了线程间的互斥性和公平性,不会因自旋消耗过多CPU资源,阻塞未获得锁的线程,保证了系统的稳定性。
缺点
:获取和释放锁涉及操作系统层面的信号量操作,导致较大的上下文切换开销,因此在高并发、锁竞争激烈的场景下性能较低。
适用场景
:适用于高度竞争性的环境,即大量并发线程同时请求同一锁资源的情况。
案例:
public class HeavyweightLockExample {
private static final Object LOCK = new Object();
public void criticalSection() {
synchronized (LOCK) {
// 在大量并发线程竞争同一锁时,重量级锁能确保公平性和稳定性
accessSharedResource();
}
}
private void accessSharedResource() {
// 访问公共资源,如数据库连接、文件写入等耗时较长的操作...
}
}
复制代码
综上所述,根据应用中的具体并发模式和锁争用情况,合理选择合适的锁类型至关重要。在实际编程中,JVM会根据实际情况自动进行锁状态的调整和升级,但开发人员也应具备理解这些锁机制的能力,并适时调整JVM参数以优化程序性能。例如,若确定应用程序不存在偏向锁的优势场景,可考虑禁用偏向锁功能。
总结与建议
Java多线程中,synchronized关键字及锁机制的运用涉及到从偏向锁到轻量级锁再到重量级锁的动态升级过程。在设计并发程序时,理解并合理选择锁策略对于提高系统性能至关重要。
偏向锁
旨在优化单一线程访问临界区的场景,通过记录当前持有锁的线程ID来避免无竞争时的额外开销。但当其他线程尝试获取锁时,需撤销偏向锁,并可能升级为轻量级锁。
public class BiasedLockDemo {
private int count;
public void increment() {
synchronized (this) {
// 偏向锁适用于只有一个线程长期执行此方法的情况
count++;
}
}
}
复制代码
轻量级锁
利用CAS操作和自旋机制,减少线程阻塞带来的上下文切换成本,在低竞争环境下提升响应速度。然而,若存在持续锁竞争,过多的自旋可能导致CPU空耗,此时会转为重量级锁。
public class LightweightLockDemo {
private final Object lock = new Object();
public void process() {
synchronized (lock) {
// 在短暂且交替访问同步块的情况下,轻量级锁能提供较好的性能
doWork();
}
}
private void doWork() {
// 执行快速计算或读取共享资源的操作...
}
}
复制代码
重量级锁
虽然开销较大,但确保了互斥性和公平性,尤其适合于高度竞争的同步场景。它通过操作系统互斥量实现,能够防止长时间占用CPU资源的自旋等待。
public class HeavyweightLockDemo {
private static final Object LOCK = new Object();
public void criticalSection() {
synchronized (LOCK) {
// 当多个线程频繁争夺同一锁资源时,重量级锁能提供稳定的保护
accessSharedResource();
}
}
private void accessSharedResource() {
// 访问需要严格同步控制的公共资源...
}
}
复制代码
在实际开发中,JVM默认启用偏向锁和轻量级锁功能,但根据具体应用场景,可以通过调整JVM参数如-XX:UseBiasedLocking、-XX:+/-UseLightweightLocking等来控制锁行为。同时,关注代码结构,尽可能减少不必要的锁竞争,优化数据结构,是提高多线程程序效率的关键所在。通过深入理解锁升级机制和每种锁的特点,开发者可以更好地权衡并发处理中的性能和安全性问题。
本文使用
markdown.com.cn
排版
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
本帖子中包含更多资源
您需要
登录
才可以下载或查看,没有账号?
立即注册
x
回复
使用道具
举报
0 个回复
正序浏览
返回列表
快速回复
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
or
立即注册
本版积分规则
发表回复
回帖并转播
发新帖
回复
怀念夏天
金牌会员
这个人很懒什么都没写!
楼主热帖
CVE-2017-12635 Couchdb 垂直权限绕过 ...
WEB安全基础入门—操作系统命令注入(s ...
【牛客】8 企业真题
Redis 原理 - Set
IOS手机Charles抓包
恭喜,成功入坑 GitHub 。。。 ...
数据库(Oracle 11g)使用expdp每周进 ...
KubeSphere3.3 私有云部署,在linux上 ...
map和flatMap的区别
java中Long和Integer缓存-128~127的简 ...
标签云
存储
服务器
快速回复
返回顶部
返回列表