0

0

Java多线程同步:解决线程饥饿问题的策略与实践

碧海醫心

碧海醫心

发布时间:2025-10-18 09:55:06

|

615人浏览过

|

来源于php中文网

原创

Java多线程同步:解决线程饥饿问题的策略与实践

本文探讨在java多线程环境中,如何通过不同策略解决锁竞争导致的线程饥饿问题。从简单的线程休眠到随机休眠,再到更高级的`wait()`和`notifyall()`机制,文章详细分析了各种方法的适用场景、优缺点及其在确保线程公平性方面的作用,旨在提供一套全面的多线程同步实践指南。

在多线程编程中,当多个线程尝试访问共享资源并需要获取锁时,可能会出现线程饥饿(Thread Starvation)问题。线程饥饿是指某个线程或一组线程由于优先级、调度策略或其他原因,长时间无法获取到所需的资源或执行机会,从而导致其任务无法完成。尤其当存在一个“无限循环”的线程频繁获取并释放锁时,其他等待获取相同锁的线程可能会长时间得不到执行。

线程饥饿问题的初步应对:引入休眠

为了缓解一个高频次执行的线程(例如在一个无限循环中运行的线程)对锁的持续占用,一种直观的策略是在该线程释放锁后引入短暂的休眠。这种做法的目的是让出CPU时间片,给其他等待锁的线程提供获取锁的机会。

考虑以下场景,一个线程在一个无限循环中尝试获取一个ReentrantLock,执行一些操作后释放锁,然后休眠片刻:

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

public class LockContentionExample {
    private final Lock writeLock = new ReentrantLock(true); // 使用公平锁

    public void infiniteLoopTask() {
        while (true) {
            try {
                // 尝试获取锁,设置超时时间防止无限等待
                if (writeLock.tryLock(100, TimeUnit.MILLISECONDS)) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " acquired lock and doing something.");
                        // 模拟业务操作
                        Thread.sleep(50); 
                    } finally {
                        writeLock.unlock();
                        System.out.println(Thread.currentThread().getName() + " released lock.");
                    }
                } else {
                    System.out.println(Thread.currentThread().getName() + " failed to acquire lock, retrying...");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.err.println(Thread.currentThread().getName() + " interrupted.");
                break;
            }

            // 关键点:释放锁后休眠,给其他线程机会
            try {
                Thread.sleep(10); // 固定休眠时间
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.err.println(Thread.currentThread().getName() + " interrupted during sleep.");
                break;
            }
        }
    }

    public static void main(String[] args) {
        LockContentionExample example = new LockContentionExample();
        new Thread(example::infiniteLoopTask, "InfiniteLoopThread").start();

        // 模拟一个调度任务线程
        new Thread(() -> {
            while (true) {
                try {
                    if (example.writeLock.tryLock(100, TimeUnit.MILLISECONDS)) {
                        try {
                            System.out.println(Thread.currentThread().getName() + " acquired lock for scheduled task.");
                            Thread.sleep(20); 
                        } finally {
                            example.writeLock.unlock();
                            System.out.println(Thread.currentThread().getName() + " released lock after scheduled task.");
                        }
                    } else {
                        System.out.println(Thread.currentThread().getName() + " failed to acquire lock for scheduled task, retrying...");
                    }
                    Thread.sleep(500); // 调度任务间隔
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }, "ScheduledTaskThread").start();

        // 模拟另一个手动触发的任务线程
        new Thread(() -> {
            try {
                Thread.sleep(1500); // 延迟启动
                if (example.writeLock.tryLock(500, TimeUnit.MILLISECONDS)) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " acquired lock for manual task.");
                        Thread.sleep(100); 
                    } finally {
                        example.writeLock.unlock();
                        System.out.println(Thread.currentThread().getName() + " released lock after manual task.");
                    }
                } else {
                    System.out.println(Thread.currentThread().getName() + " failed to acquire lock for manual task.");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "ManualTaskThread").start();
    }
}

在上述代码中,InfiniteLoopThread在每次释放锁后休眠10毫秒。对于只有两个线程(一个无限循环线程和一个调度任务线程)竞争锁的简单场景,这种固定休眠时间通常能够有效缓解饥饿问题,因为休眠提供了一个可预测的窗口,允许另一个线程尝试获取锁。

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

多线程竞争下的优化:引入随机休眠

当竞争锁的线程数量增加到三个或更多时,固定休眠时间可能会再次导致饥饿。例如,如果线程A(无限循环)释放锁,线程B和线程C都在等待。如果线程B总是比线程C更快地尝试获取锁,并且在线程A释放锁的瞬间成功获取,那么线程C可能仍然会长时间无法获取到锁。这种情况下,调度变得可预测,导致某些线程持续被“跳过”。

为了打破这种可预测性,可以引入随机休眠时间。通过让线程休眠一个随机的时长(例如,在5到100毫秒之间),可以增加所有等待线程获取锁的机会,从而提高公平性。

Designs.ai
Designs.ai

AI设计工具

下载
// ... (代码其余部分与上例相同)

// 关键点:释放锁后休眠,引入随机性
try {
    // 假设 RandomUtil.nextInt(min, max) 返回一个 min 到 max-1 之间的随机整数
    // 这里使用 Java 标准库的 Random 类模拟
    int randomSleepTime = new java.util.Random().nextInt(96) + 5; // 5到100毫秒
    Thread.sleep(randomSleepTime); 
    System.out.println(Thread.currentThread().getName() + " sleeping for " + randomSleepTime + "ms (random).");
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    System.err.println(Thread.currentThread().getName() + " interrupted during random sleep.");
    break;
}

// ... (代码其余部分与上例相同)

引入随机休眠的好处在于,它使得线程B和线程C尝试获取锁的时机变得不确定,从而增加了每个线程获得锁的概率,减少了特定线程持续饥饿的可能性。然而,无论是固定休眠还是随机休眠,都存在性能开销,因为线程在休眠期间不执行任何有用的工作。

更健壮的解决方案:wait() 和 notifyAll()

尽管休眠策略在某些简单场景下有效,但它本质上是一种基于“忙等待”(polling)和猜测的机制。更推荐和健壮的解决方案是使用Java提供的Object.wait()和Object.notifyAll()机制,这是一种基于协作的线程通信方式,能够更高效、更公平地处理线程间的资源竞争。

wait()和notifyAll()通常与synchronized关键字配合使用,它们依赖于对象的内部监视器(monitor)。当一个线程调用wait()时,它会释放当前持有的监视器锁,并进入等待状态,直到被其他线程notify()或notifyAll()唤醒。

以下是一个使用wait()和notifyAll()来协调线程访问共享资源的示例。在这个模型中,不再是线程忙于尝试获取锁,而是当资源不可用时线程进入等待状态,当资源可用时被通知唤醒。

import java.util.Queue;
import java.util.LinkedList;

public class WaitNotifyExample {
    private final Queue sharedQueue = new LinkedList<>();
    private final int CAPACITY = 5; // 队列容量

    // 生产者任务:模拟无限循环线程,生产数据并放入队列
    public void producerTask() {
        int item = 0;
        while (true) {
            synchronized (sharedQueue) {
                // 如果队列已满,生产者等待
                while (sharedQueue.size() == CAPACITY) {
                    try {
                        System.out.println("Producer: Queue is full, waiting...");
                        sharedQueue.wait(); // 释放锁并等待
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        return;
                    }
                }

                // 队列不满,生产数据
                sharedQueue.add(item);
                System.out.println("Producer produced: " + item);
                item++;

                sharedQueue.notifyAll(); // 通知所有等待的消费者
            }
            try {
                Thread.sleep(100); // 模拟生产间隔
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
        }
    }

    // 消费者任务:模拟调度任务线程或手动触发任务线程,从队列消费数据
    public void consumerTask(String name) {
        while (true) {
            synchronized (sharedQueue) {
                // 如果队列为空,消费者等待
                while (sharedQueue.isEmpty()) {
                    try {
                        System.out.println(name + ": Queue is empty, waiting...");
                        sharedQueue.wait(); // 释放锁并等待
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        return;
                    }
                }

                // 队列不空,消费数据
                int consumedItem = sharedQueue.remove();
                System.out.println(name + " consumed: " + consumedItem);

                sharedQueue.notifyAll(); // 通知所有等待的生产者/其他消费者
            }
            try {
                Thread.sleep(200); // 模拟消费间隔
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
        }
    }

    public static void main(String[] args) {
        WaitNotifyExample example = new WaitNotifyExample();

        new Thread(example::producerTask, "ProducerThread").start();
        new Thread(() -> example.consumerTask("ConsumerThread-1"), "ConsumerThread-1").start();
        new Thread(() -> example.consumerTask("ConsumerThread-2"), "ConsumerThread-2").start();
    }
}

在wait()和notifyAll()模型中:

  • 线程不再通过忙等待或周期性休眠来猜测锁是否可用。
  • 当一个线程需要某个条件(例如,队列非空或非满)才能继续执行时,它调用wait(),从而释放锁并进入等待状态,直到条件满足并被notify()或notifyAll()唤醒。
  • 当另一个线程改变了条件(例如,向队列添加了元素或移除了元素)后,它调用notifyAll()来唤醒所有等待在该对象上的线程,这些被唤醒的线程将重新竞争锁并检查条件。
  • JVM在调度notifyAll()唤醒的线程时,会尝试提供更公平的调度,从而减少饥饿的风险。

注意事项与总结

  1. 锁的类型选择: ReentrantLock提供了比synchronized更灵活的功能,如公平锁(new ReentrantLock(true)),它会尽量保证等待时间最长的线程优先获取锁,但公平锁会带来一定的性能开销。wait()/notifyAll()则与synchronized关键字(即对象的内部监视器)紧密绑定。在选择同步机制时,需根据具体需求权衡。
  2. try-finally的重要性: 无论使用ReentrantLock还是synchronized,务必在finally块中释放锁或退出同步块,以防止因异常导致锁无法释放,造成死锁。
  3. wait()的条件判断: 始终在循环中调用wait(),即while (condition)而不是if (condition)。这是因为线程可能被虚假唤醒(spurious wakeup),或者在被唤醒后,条件可能再次变得不满足。
  4. notify() vs notifyAll(): notify()只唤醒一个等待线程,而notifyAll()唤醒所有等待线程。在不确定哪个线程应该被唤醒的情况下,或者当有多种类型的线程等待时,使用notifyAll()更安全,以避免饥饿。
  5. 性能考量: 线程休眠虽然简单,但会造成CPU资源的浪费,因为线程在休眠期间不执行任何有效工作。wait()/notifyAll()机制通过让线程进入等待状态,减少了CPU的忙等待,通常更为高效。
  6. 更高级的并发工具 Java并发包(java.util.concurrent)提供了更高级、更易用的并发工具,如Semaphore(信号量)、CountDownLatch(倒计时锁)、CyclicBarrier(循环屏障)以及BlockingQueue(阻塞队列)等,它们可以帮助开发者更优雅地解决复杂的线程同步问题,避免手动实现wait()/notifyAll()的复杂性。例如,上述生产者-消费者模型可以直接使用ArrayBlockingQueue等阻塞队列来实现,代码会更加简洁和健壮。

综上所述,解决多线程饥饿问题需要根据具体场景选择合适的策略。从简单的线程休眠到随机休眠,再到基于wait()/notifyAll()的协作机制,每种方法都有其适用范围和局限性。对于复杂的并发场景,优先考虑使用Java并发包中提供的专业工具,以确保代码的健壮性、可维护性和性能。

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

通义千问
通义千问

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

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

778

2023.08.22

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

94

2023.09.25

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

503

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

166

2025.12.24

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

14

2026.01.21

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

15

2026.01.21

Java 并发编程高级实践
Java 并发编程高级实践

本专题深入讲解 Java 在高并发开发中的核心技术,涵盖线程模型、Thread 与 Runnable、Lock 与 synchronized、原子类、并发容器、线程池(Executor 框架)、阻塞队列、并发工具类(CountDownLatch、Semaphore)、以及高并发系统设计中的关键策略。通过实战案例帮助学习者全面掌握构建高性能并发应用的工程能力。

87

2025.12.01

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

141

2026.01.28

包子漫画在线官方入口大全
包子漫画在线官方入口大全

本合集汇总了包子漫画2026最新官方在线观看入口,涵盖备用域名、正版无广告链接及多端适配地址,助你畅享12700+高清漫画资源。阅读专题下面的文章了解更多详细内容。

24

2026.01.28

热门下载

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

精品课程

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

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 7.8万人学习

Java 教程
Java 教程

共578课时 | 52.6万人学习

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

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