JAVA锁机制:对象锁与类锁

JAVA锁机制:对象锁与类锁

在多线程编程中,合理使用锁机制是保证数据一致性和线程安全的关键。本文将通过示例详细讲解 Java 中的对象锁和类锁的原理、用法及区别。

一、未加锁的并发问题

先看一段未加锁的代码:

public class SynchronizedTest {private int shareField = 0;public void add() {shareField++;System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);}public static void main(String[] args) {SynchronizedTest sync = new SynchronizedTest();new Thread(() -> {for (int n = 0; n < 100000; n++) {sync.add();}}).start();new Thread(() -> {for (int n = 0; n < 100000; n++) {sync.add();}}).start();}
}

上述代码启动两个线程,每个线程各自循环10万次,对 shareField 进行自增。理论上,最终 shareField 应为 200000,但实际运行结果往往小于 200000:

当前线程:Thread-0 当前的shareField为:199994
当前线程:Thread-0 当前的shareField为:199995
...

原因在于多个线程并发修改同一变量,导致数据竞争。


二、对象锁

对象锁用于保护同一个实例的资源,确保同一时刻只有一个线程能访问被锁定的代码块。

1. 锁定非静态方法

public class SynchronizedTest {private int shareField = 0;public synchronized void add() {shareField++;System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);}// 省略 main 方法
}

synchronized 修饰非静态方法时,锁的是当前实例对象 (this)。

2. 锁定 this 对象(代码块)

有时只需对方法中的部分代码加锁,可以使用同步代码块:

public void add() {synchronized (this) {shareField++;System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);}
}

3. 锁定特定对象

也可以指定其他对象作为锁:

private final Object obj = new Object();
public void add() {synchronized (obj) {shareField++;System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);}
}
对象锁特点
  • 锁对象:当前实例(this)或指定对象
  • 多线程访问同一实例的同步方法时互斥
  • 不同实例之间互不影响

三、类锁

类锁用于保护类级别的资源(如静态变量),确保同一时刻只有一个线程能访问被锁定的静态资源。

1. 锁定静态方法

public static synchronized void add() {shareField++;System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);
}

synchronized 修饰静态方法时,锁的是类的 Class 对象。

2. 锁定 class 对象

public void add() {synchronized (SynchronizedTest.class) {shareField++;System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);}
}

3. 锁定静态实例变量

private static final Object obj = new Object();
public void add() {synchronized (obj) {shareField++;System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);}
}
类锁特点
  • 锁对象:类的 Class 对象或静态实例变量
  • 所有实例共享同一把锁,实现全局互斥

四、对象锁与类锁的区别

选择对象锁还是类锁,取决于需要保护的变量是实例级还是类级(静态)。

例如:

public class SynchronizedTest {private static int shareField = 0;public void add() {synchronized (this) {shareField++;System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);}}public static void main(String[] args) {new Thread(() -> {SynchronizedTest sync1 = new SynchronizedTest();for (int n = 0; n < 100000; n++) {sync1.add();}}).start();new Thread(() -> {SynchronizedTest sync2 = new SynchronizedTest();for (int n = 0; n < 100000; n++) {sync2.add();}}).start();}
}

上述代码中,两个线程分别操作不同实例,但都修改静态变量 shareField。此时对象锁无法保证线程安全,需使用类锁:

public void add() {synchronized (SynchronizedTest.class) {shareField++;System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);}
}

五、总结

特性对象锁类锁
锁对象当前实例(this)/obj类的Class对象/静态实例变量
作用范围同一实例间互斥所有实例间互斥
适用场景保护实例级变量保护类级变量(静态变量)
并发影响不同实例间无互斥所有实例共享同一把锁
实现方式synchronized方法/代码块static synchronized方法/class锁对象

合理选择锁的类型,是实现高效并发和线程安全的关键。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/479.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

课程目录:腾讯混元3D × Unity3D全流程开发

结合腾讯混元3D与Unity3D的设计课程&#xff0c;可构建一套覆盖“AI辅助创作→模型优化→引擎集成→实战开发”的全链路学习体系。以下是系统化的课程框架及资源推荐&#xff1a; &#x1f4da; 一、基础入门&#xff1a;双工具核心操作 Unity3D基础 界面与工作流&#xff1a;场…

电子电气架构 --- 实时系统评价的概述

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…

Redis 的穿透、雪崩、击穿

Redis 的穿透、雪崩、击穿 1、缓存穿透 定义 缓存穿透是指查询一个不存在的数据&#xff0c;由于缓存中没有该数据&#xff0c;每次请求都会直接访问数据库&#xff0c;导致数据库压力过大 产生原因 恶意攻击&#xff1a;攻击者故意请求大量不存在的key&#xff0c;导致请求直…

《map和set的使用介绍》

引言&#xff1a; 上次我们学习了第一个高阶数据结构—二叉搜索树&#xff0c;趁热打铁&#xff0c;今天我们就再来学习两个数据结构—map和set。 一&#xff1a;序列式容器和关联式容器 前面我们已经接触过STL中的部分容器如&#xff1a;string、vector、list、deque、arra…

C#学习日记

命名空间 知识点一 命名空间基本概念 概念 命名空间是用来组织和重用代码的 作用 就像是一个工具包&#xff0c;类就像是一件一件的工具&#xff0c;都是申明在命名空间中的 知识点二 命名空间的使用 基本语法 namespace 命名空间名 {类类 } namespace MyGame {class GameO…

OSI网络通信模型详解

OSI 模型就是把这整个过程拆解成了 7 个明确分工的步骤&#xff0c;每一层只负责自己那一摊事儿&#xff0c;这样整个系统才能顺畅运转&#xff0c;出了问题也容易找到“锅”在谁那。 核心比喻&#xff1a;寄快递 &#x1f4e6; 想象你要把一份重要的礼物&#xff08;你的数据…

高并发网络通信Netty之空轮询问题

一、问题背景 在 NioEventLoop 事件循环中&#xff0c;Selector 一次次 select() 返回为 0&#xff0c;且没有事件被触发&#xff0c;形成空转&#xff0c;导致 CPU 占用 100%&#xff0c;系统资源白白浪费。这种情况尤其在 高并发、连接数多、IO事件少 的场景下更容易出现。 …

Nginx+Tomcat负载均衡群集

一、NginxTomcat 负载均衡、动静分离 1、Tomcat 简介 名称由来&#xff1a;Tomcat 最初由 Sun 的软件构架师詹姆斯・邓肯・戴维森开发&#xff0c;后变为开源项目并由 Sun 贡献给 Apache 软件基金会。因 O’Reilly 开源项目常以动物命名相关书籍&#xff0c;他希望动物能自我照…

Linux下nginx访问路径页面

第一步&#xff1a;通过Xshell在虚拟机中下载nginx sudo apt-get install nginx 第二步&#xff1a;进入nginx配置页面 cd /etc/nginx 我这里创建了一个html文件夹 在进入去创建页面并且重新加载 boahuboahu-VMware-Virtual-Platform:/$ cd /etc/nginx boahuboahu-VMware-Vir…

python实战项目76:51job数据采集与分析

python实战项目76:51job数据采集与分析 一、数据采集二、数据预处理2.1 导入相关库、读取数据2.2 查看数据2.3 处理数据、删除重复值、删除空值2.4 处理薪资水平字段数据三、数据可视化3.1 不同公司规模招聘岗位数量分布3.2 不同公司性质招聘岗位数量分布3.3 不同年限要求招聘岗…

OPENGLPG第九版学习 - 纹理与帧缓存 part1

文章目录 6.1 纹理综述6.2 基木纹理类型6.3 创建并初始化纹理代理纹理 6.4 指定纹理数据6.4.1 显式设置纹理数据将静态数据载入到纹理对象 6.4.2 从缓存(目标对象GL_PIXEL_UNPACK_BUFFER)中加载纹理6.4.3 从文件加载图像(DDS为例)读取一个图像文件并返回内存中的纹素数据将纹素…

Redis 持久化机制详解:RDB、AOF 原理与面试最佳实践(AOF篇)

在上一章我们深入学习了 Redis 中重要的数据持久化机制 ——RDB&#xff08;Redis Database&#xff09;&#xff0c;了解了其通过周期性快照将数据以二进制文件形式保存到磁盘的原理&#xff0c;包括触发条件、文件结构以及优缺点等核心内容。 Redis 持久化机制详解&#xff…