当前位置: 首页 > news >正文

Java学习——day27(线程间通信与死锁防范)

文章目录

  • 1. 线程间通信
    • 1.1 基本原理
    • 1.2 使用场景
  • 2. 死锁与防范
    • 2.1 死锁产生的原因
    • 2.2 避免策略
  • 3. 实践:生产者—消费者模型示例
    • 3.1 完整示例代码
    • 3.2 代码详解
  • 4. 总结与思考

1. 线程间通信

1.1 基本原理

  • wait() 方法
    当线程调用对象上的 wait() 方法时,当前线程会释放该对象的锁,并进入等待状态,直到其他线程调用同一对象上的 notify() 或 notifyAll() 方法。
    注意:必须在 synchronized 块中调用 wait()。

  • notify() 方法
    当线程调用 notify() 方法时,会随机唤醒等待该对象锁的一个线程,使其重新进入可运行状态。
    注意:同样需要在 synchronized 块中调用。

  • notifyAll() 方法
    与 notify() 类似,但会唤醒所有等待该对象锁的线程。
    一般在生产—消费者模型中,唤醒所有等待的线程以便竞争资源。

1.2 使用场景

  • 线程间协调 :例如在生产者—消费者模型中,生产者生产数据后通知消费者;消费者数据不足时,等待生产者通知。

  • 保证顺序与互斥 :通过 wait/notify 机制实现线程按照预定顺序操作共享数据,确保数据一致性。

2. 死锁与防范

2.1 死锁产生的原因

  • 死锁(Deadlock) :多个线程相互等待对方释放资源,导致所有线程都无法继续执行。

  • 死锁的四个必要条件:
    1.互斥条件 :资源不能共享。
    2.请求与保持条件 :线程已获得资源,但仍在等待其它资源,同时保持已获得资源。
    3.不剥夺条件 :资源不能被强制收回,只能在任务完成后由线程主动释放。
    4.循环等待条件 :存在一组线程,形成循环等待资源的关系。

2.2 避免策略

  • 破坏循环等待条件 :设定资源获取顺序,所有线程按照固定次序获取资源。
  • 加锁超时 :使用可中断的锁申请,当等待时间超时后放弃当前申请,避免死锁。
  • 减少锁的粒度 :尽量减少临界区代码,使用细粒度锁降低冲突几率。
  • 使用死锁检测工具 :监控程序运行时的线程状态,以便及时发现和解决死锁。

3. 实践:生产者—消费者模型示例

下面的示例中,我们实现一个简单的生产者—消费者模型,使用共享缓冲区作为资源,生产者和消费者通过 wait/notify 协调工作。
代码中包含三个部分:

  • Buffer 类 :共享缓冲区,内部使用队列存储数据,提供 synchronized 方法进行生产和消费操作。
  • Producer 类 :生产者线程,不断向缓冲区添加数据,当缓冲区满时等待。
  • Consumer 类 :消费者线程,不断从缓冲区取出数据,当缓冲区为空时等待。

3.1 完整示例代码

// ProducerConsumerDemo.java

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

public class ProducerConsumerDemo {

    public static void main(String[] args) {
        // 创建共享缓冲区,容量为 5
        Buffer buffer = new Buffer(5);
        
        // 创建生产者和消费者线程
        Thread producerThread = new Thread(new Producer(buffer), "Producer");
        Thread consumerThread = new Thread(new Consumer(buffer), "Consumer");
        
        // 启动线程
        producerThread.start();
        consumerThread.start();
    }
}

// 共享缓冲区类,使用 wait/notify 协调生产者与消费者
class Buffer {
    private Queue<Integer> queue = new LinkedList<>();
    private int capacity;

    public Buffer(int capacity) {
        this.capacity = capacity;
    }
    
    // 生产数据:当缓冲区满时等待,否则添加数据,并调用 notifyAll 通知消费者
    public synchronized void produce(int value) throws InterruptedException {
        while (queue.size() == capacity) {
            System.out.println(Thread.currentThread().getName() + " 等待,缓冲区已满!");
            wait();  // 释放锁并等待
        }
        queue.add(value);
        System.out.println(Thread.currentThread().getName() + " produced: " + value);
        notifyAll();  // 通知等待的线程
    }
    
    // 消费数据:当缓冲区为空时等待,否则取出数据,并调用 notifyAll 通知生产者
    public synchronized int consume() throws InterruptedException {
        while (queue.isEmpty()) {
            System.out.println(Thread.currentThread().getName() + " 等待,缓冲区为空!");
            wait();
        }
        int value = queue.poll();
        System.out.println(Thread.currentThread().getName() + " consumed: " + value);
        notifyAll();
        return value;
    }
}

// 生产者线程类
class Producer implements Runnable {
    private Buffer buffer;
    
    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }
    
    @Override
    public void run() {
        int value = 0;
        while (true) {
            try {
                buffer.produce(value++);
                Thread.sleep(500);  // 模拟生产过程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 消费者线程类
class Consumer implements Runnable {
    private Buffer buffer;
    
    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }
    
    @Override
    public void run() {
        while (true) {
            try {
                buffer.consume();
                Thread.sleep(1000); // 模拟消费过程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:
运行 ProducerConsumerDemo 后,控制台输出可能类似如下(注意线程调度具有随机性,具体输出顺序可能交替变化):

Producer produced: 0
Consumer consumed: 0
Producer produced: 1
Producer produced: 2
Producer produced: 3
Producer produced: 4
Producer produced: 5
Producer 等待,缓冲区已满!
Consumer consumed: 1
Producer produced: 6
Consumer consumed: 2
Consumer consumed: 3
...

说明:

  • 生产者:

    • 会先产生数据(例如 0、1、2…)并打印 “Producer produced: X”。
    • 当缓冲区中的数据达到容量(容量设置为 5)时,生产者会打印 “Producer 等待,缓冲区已满!” 并调用 wait() 进入等待状态,直到消费者取走数据释放空间。
  • 消费者:

    • 当缓冲区非空时消费数据,打印 “Consumer consumed: X”,当缓冲区为空时会打印 “Consumer 等待,缓冲区为空!” 并调用 wait()。
    • 由于消费者休眠时间较长(1000ms),通常会使得生产者较快地填满缓冲区,从而观察到生产者“等待”的输出。
  • 协调工作:

    • 每当生产者成功生产数据后调用 notifyAll(),唤醒等待中的消费者;同理,消费者消费数据后调用 notifyAll() 来唤醒等待生产者。
    • 随着线程不断重复调用生产和消费操作,整个系统保持一个平衡状态,确保数据不会丢失或重复。

3.2 代码详解

  • Buffer 类:

    • 使用 synchronized 修饰方法,保证同一时刻只有一个线程能够操作队列。
    • 生产者调用 produce 方法时,若缓冲区已满(队列大小达到容量),调用wait()进入等待状态,并打印相应信息;成功生产后调用 notifyAll() 通知其它等待线程。
    • 消费者调用consume方法时,若缓冲区为空,调用wait()进入等待状态;成功消费后调用 notifyAll() 进行通知。
  • Producer 与 Consumer 类:

    • 生产者线程不断生产数据,并调用缓冲区的 produce 方法。
    • 消费者线程不断消费数据,并调用缓冲区的 consume 方法。
    • 模拟生产与消费过程中的延时,通过 Thread.sleep() 模拟执行耗时,便于观察线程间的协调效果。
  • ProducerConsumerDemo 主类:

    • main方法中,创建共享的 Buffer 实例,并启动一个生产者和一个消费者线程。运行过程中,通过等待和通知,生产者与消费者在共享缓冲区间交替执行,避免了数据丢失和竞争问题。

4. 总结与思考

  • 线程间通信:

    • 通过·wait()notify()notifyAll()实现线程间的协调,确保在临界区内的线程能够互相告知状态变化,从而达到数据共享和顺序控制。
  • 死锁防范:

    • 学习了死锁的四个必要条件,并通过设计合理的同步代码(避免不必要的嵌套锁定和严格的锁获取顺序)来防止死锁的发生。
    • 编写协作代码时,应尽量缩小 synchronized 块的范围,确保及时释放锁。

相关文章:

  • 数据可视化 —— 多边图应用(大全)
  • SageAttention2
  • 设计一个简单的权限管理系统
  • Shell 编程之条件语句
  • 项目文章 ▏磷酸化依赖的VaMYB4a通过抑制VaPIF3和激活VaCBF4调节葡萄藤冷胁迫
  • 蓝桥杯备战
  • 传统门店VS智慧门店:电能物联网平台在连锁行业的节能应用
  • 线代第五课:行列式按一行(列)展开
  • Ubuntu ROS 对应版本
  • 破解root密码
  • 力扣 — — 最长公共子序列
  • 【游戏安全】基于协议场景的挖掘
  • 腾讯云COS与ZKmall 开源商城的存储集成方案
  • Java基础 - 泛型(常见用法)
  • 免费报名 | Ansys Speos 2025 R1新功能更新网络研讨会
  • MacOS红队常用攻击命令
  • 嵌入式系统的历史与发展​
  • Linux 系统中打包与压缩
  • stm32工程,拷贝到另一台电脑编译,错误提示头文件找不到cannot open source input file “core_cm4.h”
  • Python数据可视化-第7章-绘制3D图表和统计地图
  • 最大的域名注册网站是那个/广告优化师适合女生吗
  • 网站突然打不开了/提高网站排名
  • 最大郑州网站建设公司/宁波网络推广联系方式
  • 建筑网站建设需要注意什么/seo站长工具查询系统
  • 如何给网站做优化代码/关键词筛选
  • 秦皇岛市海港区建设局网站/全网营销推广软件