Java 中的 synchronized 和 Lock:如何保证线程安全

news2025/6/1 5:35:26

Java 中的 synchronized 和 Lock:如何保证线程安全

引言

在 Java 多线程编程中,线程安全是一个核心问题。当多个线程同时访问共享资源时,可能会导致数据不一致或其他不可预期的结果。synchronized关键字和Lock接口是 Java 中实现线程同步的两种主要方式,本文将深入探讨它们的工作原理、使用场景及源码实现,并通过代码样例解析其线程安全机制。

一、线程安全基础概念

1.1 什么是线程安全?

线程安全是指当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为。

1.2 线程安全问题的根源

  • 原子性:一个或多个操作在 CPU 执行过程中被中断
  • 可见性:一个线程修改了共享变量的值,其他线程未能及时看到最新的值
  • 有序性:程序执行的顺序可能与代码顺序不一致(指令重排序)

1.3 Java 内存模型(JMM)

Java 内存模型规定了线程之间的可见性和有序性,其核心概念包括:

  • 主内存:所有变量存储的区域
  • 工作内存:每个线程独立拥有的内存区域,存储线程使用的变量副本
  • 内存屏障:保证特定操作的执行顺序和可见性

二、synchronized 关键字

2.1 synchronized 的基本用法

synchronized关键字可以修饰方法或代码块,确保同一时刻只有一个线程可以执行该代码:

public class SynchronizedExample {
    private int count = 0;

    // 同步方法
    public synchronized void increment() {
        count++;
    }

    // 同步代码块
    public void decrement() {
        synchronized (this) {
            count--;
        }
    }

    // 静态同步方法
    public static synchronized void staticMethod() {
        // ...
    }
}

2.2 synchronized 的底层实现

2.2.1 对象头与 Monitor

在 Java 中,每个对象都有一个对象头(Object Header),其中包含了 Mark Word。当对象被synchronized修饰时,Mark Word 会存储指向 Monitor 对象的指针。

Monitor 是 Java 中实现同步的基础,它是一个对象级的同步机制,本质上是一个锁的实现。每个 Java 对象都可以关联一个 Monitor,当一个线程尝试访问被synchronized修饰的代码块时,它必须先获得该对象的 Monitor。

2.2.2 字节码层面的实现

通过javap -v命令查看编译后的字节码,可以看到synchronized代码块使用monitorentermonitorexit指令实现:

public void decrement();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
        stack=2, locals=3, args_size=1
            0: aload_0
            1: dup
            2: astore_1
            3: monitorenter            // 进入同步块
            4: aload_0
            5: dup
            6: getfield      #2        // Field count:I
            9: iconst_1
            10: isub
            11: putfield      #2       // Field count:I
            14: aload_1
            15: monitorexit           // 正常退出同步块
            16: goto          24
            19: astore_2
            20: aload_1
            21: monitorexit           // 异常退出同步块
            22: aload_2
            23: athrow
            24: return
2.2.3 重量级锁与轻量级锁

在 JDK 1.6 之前,synchronized 是一个重量级锁,性能较低。JDK 1.6 引入了锁升级机制,优化了 synchronized 的性能:

  • 无锁状态:对象头 Mark Word 存储对象的哈希码等信息
  • 偏向锁:单线程环境下,锁偏向第一个获得它的线程
  • 轻量级锁:多线程环境下,线程交替执行同步块,通过 CAS 操作获取锁
  • 重量级锁:多个线程同时竞争锁,向操作系统申请互斥量

2.3 synchronized 的特性

  • 可重入性:同一个线程可以多次获取同一把锁
  • 不可中断性:一旦线程获取锁,其他线程只能等待锁释放
  • 保证原子性、可见性和有序性

三、Lock 接口与 ReentrantLock

3.1 Lock 接口的基本方法

public interface Lock {
    void lock();                          // 获取锁
    void lockInterruptibly() throws InterruptedException; // 可中断获取锁
    boolean tryLock();                    // 尝试非阻塞获取锁
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 超时获取锁
    void unlock();                        // 释放锁
    Condition newCondition();             // 获取等待通知组件
}

3.2 ReentrantLock 的使用示例

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private final Lock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public void decrement() {
        lock.lock();
        try {
            count--;
        } finally {
            lock.unlock();
        }
    }
}

3.3 ReentrantLock 的源码解析

3.3.1 AQS(AbstractQueuedSynchronizer)

ReentrantLock 的核心是基于 AQS(AbstractQueuedSynchronizer)实现的。AQS 是一个用于构建锁和同步器的框架,它使用一个整型的 state 变量来表示锁的状态,并维护一个 FIFO 队列来管理等待线程。

public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        // ...
    }

    static final class NonfairSync extends Sync {
        // 非公平锁实现
    }

    static final class FairSync extends Sync {
        // 公平锁实现
    }
}
3.3.2 公平锁与非公平锁

ReentrantLock 支持公平锁和非公平锁两种模式:

  • 公平锁:按照线程请求锁的顺序获取锁
  • 非公平锁:线程可以抢占式获取锁,不考虑请求顺序
// 创建公平锁
Lock fairLock = new ReentrantLock(true);

// 创建非公平锁(默认)
Lock nonfairLock = new ReentrantLock();
3.3.3 锁的获取与释放

以非公平锁为例,lock () 方法的实现:

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
3.3.4 可重入性实现

ReentrantLock 的可重入性通过 state 变量实现:当同一个线程再次获取锁时,state 值递增;释放锁时,state 值递减。当 state 值为 0 时,表示锁已完全释放。

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

四、synchronized 与 Lock 的对比

特性synchronizedLock
语法内置语言关键字接口,需要显式调用 lock () 和 unlock ()
锁的获取自动获取和释放手动获取和释放,必须在 finally 中释放
可中断性不可中断可中断(lockInterruptibly ())
公平性非公平可选择公平或非公平
锁的状态无法判断可以判断(isLocked ())
条件变量单一条件变量可以创建多个条件变量
性能JDK 1.6 后优化,轻量级锁性能接近 Lock高并发场景下性能更优

五、线程安全实践:银行账户示例

5.1 使用 synchronized 实现

public class BankAccount {
    private double balance;

    public BankAccount(double balance) {
        this.balance = balance;
    }

    // 同步方法实现线程安全
    public synchronized void deposit(double amount) {
        balance += amount;
    }

    // 同步方法实现线程安全
    public synchronized void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
        } else {
            throw new IllegalArgumentException("余额不足");
        }
    }

    public synchronized double getBalance() {
        return balance;
    }
}

5.2 使用 ReentrantLock 实现

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BankAccount {
    private double balance;
    private final Lock lock = new ReentrantLock();

    public BankAccount(double balance) {
        this.balance = balance;
    }

    public void deposit(double amount) {
        lock.lock();
        try {
            balance += amount;
        } finally {
            lock.unlock();
        }
    }

    public void withdraw(double amount) {
        lock.lock();
        try {
            if (balance >= amount) {
                balance -= amount;
            } else {
                throw new IllegalArgumentException("余额不足");
            }
        } finally {
            lock.unlock();
        }
    }

    public double getBalance() {
        lock.lock();
        try {
            return balance;
        } finally {
            lock.unlock();
        }
    }
}

六、总结

6.1 synchronized 的适用场景

  • 简单的同步需求
  • 不需要锁的高级特性(可中断、公平性等)
  • 代码简洁性要求高

6.2 Lock 的适用场景

  • 需要可中断锁
  • 需要公平锁
  • 需要多个条件变量
  • 在高并发场景下追求更好的性能

6.3 最佳实践

  1. 优先使用 synchronized,因为它更简洁,且 JDK 1.6 后性能已经得到优化
  2. 在需要高级特性时使用 Lock
  3. 使用 Lock 时,必须在 finally 块中释放锁
  4. 避免锁的嵌套,防止死锁
  5. 对性能敏感的场景,考虑使用细粒度的锁

通过synchronizedLock,Java 提供了强大而灵活的线程同步机制,开发者可以根据具体场景选择合适的同步方式,确保多线程程序的安全性和性能。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

贪心算法应用:最大匹配问题详解

Java中的贪心算法应用:最大匹配问题详解 贪心算法是一种在每一步选择中都采取当前状态下最优的选择,从而希望导致结果是全局最优的算法策略。在Java中,贪心算法可以应用于多种问题,其中最大匹配问题是一个经典的应用场景。下面我将从基础概念到具体实现,全面详细地讲解贪…

爬虫IP代理效率优化:策略解析与实战案例

目录 一、代理池效率瓶颈的根源分析 二、六大核心优化策略 策略1&#xff1a;智能IP轮换矩阵 策略2&#xff1a;连接复用优化 策略3&#xff1a;动态指纹伪装 策略4&#xff1a;智能重试机制 三、典型场景实战案例 案例1&#xff1a;电商价格监控系统 案例2&#xff1a…

豆瓣电视剧数据工程实践:从爬虫到智能存储的技术演进(含完整代码)

通过网盘分享的文件&#xff1a;资料 链接: https://pan.baidu.com/s/1siOrGmM4n-m3jv95OCea9g?pwd4jir 提取码: 4jir 1. 引言 1.1 选题背景 在影视内容消费升级背景下&#xff0c;豆瓣电视剧榜单作为国内最具影响力的影视评价体系&#xff0c;其数据价值体现在&#xff1a…

基于微信小程序的漫展系统的设计与实现

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

基于Web的分布式图集管理系统架构设计与实践

引言&#xff1a;为什么需要分布式图集管理&#xff1f; 在现代Web图形应用中&#xff0c;纹理图集&#xff08;Texture Atlas&#xff09;技术是优化渲染性能的关键手段。传统的图集制作流程通常需要美术人员使用专业工具&#xff08;如TexturePacker&#xff09;离线制作&am…

mysql执行sql语句报错事务锁住

报错情况 1205 - Lock wait timeout exceeded; try restarting transaction先找出长时间运行的事务 SELECT * FROM information_schema.INNODB_TRX ORDER BY trx_started ASC;终止长时间运行的事务 KILL [PROCESS_ID];

Java消息队列应用:Kafka、RabbitMQ选择与优化

Java消息队列应用&#xff1a;Kafka、RabbitMQ选择与优化 在Java应用领域&#xff0c;消息队列是实现异步通信、应用解耦、流量削峰等重要功能的关键组件。Kafka和RabbitMQ作为两种主流的消息队列技术&#xff0c;各有特点和适用场景。本文将深入探讨Kafka和RabbitMQ在Java中的…

零基础设计模式——结构型模式 - 组合模式

第三部分&#xff1a;结构型模式 - 组合模式 (Composite Pattern) 在学习了桥接模式如何分离抽象和实现以应对多维度变化后&#xff0c;我们来探讨组合模式。组合模式允许你将对象组合成树形结构来表现“整体-部分”的层次结构。组合模式使得用户对单个对象和组合对象的使用具…

腾讯云国际站可靠性测试

在数字化转型加速的今天&#xff0c;企业对于云服务的依赖已从“可选”变为“必需”。无论是跨境电商的实时交易&#xff0c;还是跨国企业的数据协同&#xff0c;云服务的可靠性直接决定了业务连续性。作为中国领先的云服务提供商&#xff0c;腾讯云国际站&#xff08;Tencent …

自定义异常小练习

在开始之前,让我们高喊我们的口号&#xff1a; ​​​​​​​ 键盘敲烂,年薪百万&#xff01; 目录 键盘敲烂,年薪百万&#xff01; 异常综合练习&#xff1a; 自定义异常 异常综合练习&#xff1a; 自定义异常&#xff1a; 定义异常类写继承关系空参构造带参构造 自定…

SpringBoot整合MinIO实现文件上传

使用Spring Boot与JSP和MinIO&#xff08;一个开源对象存储系统&#xff0c;兼容Amazon S3&#xff09;进行集成&#xff0c;您可以创建一个Web应用来上传、存储和管理文件。以下是如何将Spring Boot、JSP和MinIO集成的基本步骤&#xff1a; 这个是minio正确启动界面 这个是min…

基于面向对象设计的C++日期推算引擎:精准高效的时间运算实现与运算重载工程化实践

前引&#xff1a; 在软件开发中&#xff0c;时间与日期的处理是基础但极具挑战性的任务。传统的手工日期运算逻辑往往面临闰年规则、月份天数动态变化、时区转换等复杂场景的容错难题&#xff0c;且代码冗余度高、可维护性差。本文将深入探讨如何利用C的面向对象特性与成员函数…

如何把 Microsoft Word 中所有的汉字字体替换为宋体?

Ctrl H &#xff0c;然后&#xff0c;点击更多&#xff0c;勾选使用通配符&#xff0c;查找内容中填入 [一-龥]{1,}&#xff0c; 这是 Word 通配符匹配汉字的经典写法&#xff08;匹配 Unicode 范围内的 CJK 汉字&#xff09;。 然后&#xff0c; “替换为”留空&#xff0c;点…

02. [Python+Golang+PHP]三数之和,多种语言实现最优解demo

一、问题描述&#xff1a;三数之和 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中…

倚光科技在二元衍射面加工技术上的革新:引领光学元件制造新方向​

倚光科技二元衍射面加工技术&#xff08;呈现出细腻的光碟反射纹路&#xff09; 在光学元件制造领域&#xff0c;二元衍射面的加工技术一直是行业发展的关键驱动力之一。其精准的光相位调制能力&#xff0c;在诸多前沿光学应用中扮演着不可或缺的角色。然而&#xff0c;长期以来…

驱动开发(2)|鲁班猫rk3568简单GPIO波形操控

上篇文章写了如何下载内核源码、编译源码的详细步骤&#xff0c;以及一个简单的官方demo编译&#xff0c;今天分享一下如何根据板子的引脚写自己控制GPIO进行高低电平反转。 想要控制GPIO之前要学会看自己的引脚分布图&#xff0c;我用的是鲁班猫RK3568&#xff0c;引脚分布图如…

《软件工程》第 3 章 -需求工程概论

在软件工程的开发流程中&#xff0c;需求工程是奠定项目成功基础的关键环节。它专注于获取、分析、定义和管理软件需求&#xff0c;确保开发出的软件能真正满足用户需求。接下来&#xff0c;我们将按照目录内容&#xff0c;结合 Java 代码和实际案例&#xff0c;深入讲解需求工…

VMware-MySQL主从

MySQL主从 服务器信息 服务器类型角色主机地址主机名称虚拟机master192.168.40.128test-1虚拟机slave192.168.40.129test-2 Master 配置&#xff08;192.168.40.128&#xff09; 删除自动生成的配置 /var/lib/mysql/auto.cnf [roottest-1 ~]# rm -rf /var/lib/mysql/auto.…

2023-ICLR-ReAct 首次结合Thought和Action提升大模型解决问题的能力

关于普林斯顿大学和Google Research, Brain Team合作的一篇文章, 在语言模型中协同Reasoning推理和Action行动。 论文地址&#xff1a;https://arxiv.org/abs/2210.03629 代码&#xff1a;https://github.com/ysymyth/ReAct.git 其他复现 langchain &#xff1a;https://pytho…

Rust 开发的一些GUI库

最近考虑用Rust干点什么&#xff0c;于是搜集了下资料——根据2025年最新调研结果和社区实践&#xff0c;Rust GUI库生态已形成多个成熟度不同的解决方案。以下是当前主流的GUI库分类及特点分析&#xff0c;结合跨平台支持、开发体验和实际应用场景进行综合评估&#xff1a; 一…