0

0

Java多线程并发控制:使用同步锁解决资源抢占问题

DDD

DDD

发布时间:2025-10-07 11:29:42

|

943人浏览过

|

来源于php中文网

原创

Java多线程并发控制:使用同步锁解决资源抢占问题

本文探讨了在Java多线程环境中,如何有效控制线程对共享资源的访问,以避免因并发执行导致的资源抢占和操作中断问题。针对线程在打印等关键操作中被其他线程干扰的场景,文章详细阐述了使用synchronized同步锁机制作为替代线程优先级控制的更可靠方法,确保关键代码段的原子性,从而实现有序、无冲突的并发操作。

1. 理解并发问题与线程优先级局限性

在多线程编程中,当多个线程尝试同时访问或修改同一个共享资源时,往往会导致数据不一致、操作中断或不可预测的行为。例如,在一个打印任务执行过程中,如果其他线程未经协调地插入自己的打印内容,最终的输出将是混乱且难以理解的。

面对此类问题,开发者常会首先想到通过设置线程优先级来解决。然而,Java中的线程优先级(Thread.setPriority())并不能提供严格的执行顺序保证。它仅仅是操作系统调度器的一个提示,实际的调度行为高度依赖于底层操作系统和JVM的实现,因此,依赖线程优先级来确保关键操作的原子性或互斥性是不可靠的。即使将一个线程的优先级设置得很高,也不能保证它在完成其任务前不会被其他低优先级的线程中断。

2. 核心解决方案:使用同步锁(synchronized)

为了确保共享资源在某一时刻只能被一个线程访问,Java提供了强大的同步机制。其中,synchronized关键字是实现互斥访问最常用且直接的方式。它允许开发者定义一个“临界区”(critical section),即一段代码块,在任何时刻最多只能有一个线程执行该代码块。

当一个线程进入synchronized块时,它会尝试获取与该块关联的锁。如果锁已被其他线程持有,当前线程将被阻塞,直到锁被释放。一旦线程完成synchronized块的执行,它会自动释放锁,从而允许其他等待的线程获取锁并进入临界区。

针对上述打印中断问题,我们可以通过使用一个共享的锁对象来同步所有涉及打印操作的方法。

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

海螺音乐
海螺音乐

海螺AI推出的AI音乐生成工具,可以生成个性化的音乐作品。

下载

示例代码:

public class PrinterManager {

    // 定义一个静态的、final的锁对象,所有线程共享此锁
    // 确保所有需要同步的方法都使用同一个锁实例
    public static final Object MY_PRINT_LOCK = new Object();

    /**
     * 模拟第一个打印任务
     */
    public void printTask1() {
        // 使用MY_PRINT_LOCK作为同步监视器
        // 只有获取到此锁的线程才能执行内部代码
        synchronized(MY_PRINT_LOCK) {
            System.out.println("--- 开始打印任务 1 ---");
            try {
                // 模拟打印过程中的耗时操作
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("打印内容 1.1");
            System.out.println("打印内容 1.2");
            System.out.println("--- 结束打印任务 1 ---");
        }
    }

    /**
     * 模拟第二个打印任务
     */
    public void printTask2() {
        synchronized(MY_PRINT_LOCK) {
            System.out.println("--- 开始打印任务 2 ---");
            try {
                Thread.sleep(150);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("打印内容 2.1");
            System.out.println("打印内容 2.2");
            System.out.println("--- 结束打印任务 2 ---");
        }
    }

    /**
     * 模拟第三个打印任务
     */
    public void printTask3() {
        synchronized(MY_PRINT_LOCK) {
            System.out.println("--- 开始打印任务 3 ---");
            try {
                Thread.sleep(80);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("打印内容 3.1");
            System.out.println("打印内容 3.2");
            System.out.println("--- 结束打印任务 3 ---");
        }
    }

    public static void main(String[] args) {
        PrinterManager manager = new PrinterManager();

        // 创建并启动多个线程,分别执行不同的打印任务
        new Thread(() -> manager.printTask1(), "Printer-Thread-1").start();
        new Thread(() -> manager.printTask2(), "Printer-Thread-2").start();
        new Thread(() -> manager.printTask3(), "Printer-Thread-3").start();
    }
}

代码解析:

  • public static final Object MY_PRINT_LOCK = new Object();:我们创建了一个static final的Object实例作为锁对象。
    • static:确保所有PrinterManager实例(即使有多个)都共享同一个锁对象,这样不同线程调用的不同PrinterManager实例上的方法也能正确同步。
    • final:确保MY_PRINT_LOCK引用一旦初始化后就不会改变,防止在运行时意外地将锁对象替换掉,导致同步失效。
    • new Object():一个普通的Object实例足以作为锁对象。它的唯一性是关键。
  • synchronized(MY_PRINT_LOCK) { ... }:这是同步代码块的语法。任何线程在执行{...}中的代码之前,都必须成功获取到MY_PRINT_LOCK对象的监视器锁。一旦一个线程获取了锁,其他试图获取同一锁的线程将被阻塞,直到当前线程释放锁。

通过这种方式,无论有多少个线程同时调用printTask1()、printTask2()或printTask3(),它们都必须排队等待获取MY_PRINT_LOCK,从而保证了每个打印任务都能完整、不中断地执行。

3. 注意事项与最佳实践

  1. 选择合适的锁对象:
    • 对于实例方法,可以使用this作为锁对象(synchronized(this)或直接在方法签名上加synchronized)。
    • 对于静态方法或需要跨实例同步的情况,必须使用一个类级别的锁对象,通常是一个static final的Object实例,如示例所示。
    • 避免使用字符串字面量作为锁对象(如synchronized("lock")),因为字符串常量池可能导致不同代码块意外地使用同一个锁,从而引发不可预期的死锁或过度同步。
  2. 锁粒度:
    • 粗粒度锁(锁定范围大)虽然简单,但可能降低并发性,因为很多不相关的操作也因此被序列化。
    • 细粒度锁(锁定范围小)可以提高并发性,但增加了复杂性,且容易引入死锁。应根据实际需求和性能瓶颈进行权衡。在上述打印例子中,由于打印的完整性是核心需求,粗粒度锁是合适的。
  3. 避免死锁:
    • 当多个线程需要获取多个锁时,如果获取锁的顺序不一致,就可能发生死锁。设计时应尽量保持获取锁的顺序一致性。
  4. 替代方案:java.util.concurrent.locks.ReentrantLock:
    • 对于更复杂的同步需求,例如可中断的锁获取、尝试获取锁(非阻塞)、公平锁等,ReentrantLock提供了比synchronized更灵活的控制。但对于简单的互斥访问,synchronized通常是首选,因为它由JVM管理,无需手动释放锁(避免忘记释放锁导致死锁)。
  5. 线程优先级:
    • 再次强调,不要依赖线程优先级来确保操作的原子性或互斥性。它们只是一种调度提示,不能提供强有力的并发控制。

4. 总结

在Java多线程编程中,解决资源抢占和操作中断问题的关键在于正确地使用同步机制。synchronized关键字提供了一种简单而强大的方式来确保共享资源的互斥访问,从而保证了代码的原子性和线程安全性。通过定义一个共享的锁对象并将其应用于所有需要同步的关键代码段,我们可以有效地控制线程的执行顺序,避免数据混乱,实现稳定可靠的并发应用程序。在大多数场景下,synchronized是实现线程安全的首选工具,只有在需要更高级的控制时才考虑使用java.util.concurrent.locks包下的工具类。

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

通义千问
通义千问

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

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1502

2023.10.24

字符串常量的表示方法
字符串常量的表示方法

字符串常量的表示方法:1、使用引号;2、转义字符;3、多行字符串;4、原始字符串;5、字符串连接;6、字符串字面量和对象;7、编码问题。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

140

2023.12.26

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

319

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1502

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

624

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

653

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

609

2024.04.29

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

8

2026.01.30

热门下载

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

精品课程

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

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 8万人学习

Java 教程
Java 教程

共578课时 | 53.5万人学习

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

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