多线程与锁机制

一、应用场景

      1.多线程与锁机制是并发编程中非常重要的概念。在多线程环境下,为了保证数据的一致性和避免竞态条件,需要使用锁来对共享资源进行保护。

     2.在多线程编程中,生产者-消费者模型是一个经典的场景。生产者负责生产数据,消费者负责消费数据。为了确保生产者和消费者的安全性,需要使用锁来同步它们之间的操作。

      3.在Java中,可以使用synchronized关键字或者ReentrantLock类来实现锁机制。它们的区别主要在于灵活性和功能上的不同。

  1. synchronized:synchronized是Java中内置的关键字,用于实现同步代码块或同步方法。在使用synchronized时,编译器会自动添加monitorenter和monitorexit指令,来实现对代码块或方法的加锁和解锁。synchronized比较简单易用,但功能上相对较为受限。

  2. ReentrantLock:ReentrantLock是JDK中提供的一个显式锁,在使用时需要手动进行加锁和解锁的操作。相比synchronized,ReentrantLock更加灵活,提供了更多的功能,比如可重入性、公平性、线程中断等。但是需要注意的是,在使用ReentrantLock时需要确保在finally块中进行解锁操作,以避免出现死锁的情况。

4. JVM内存模型是指Java虚拟机在执行Java程序时管理内存的规范。Java内存模型规定了线程如何与内存交互,主要包括主内存、工作内存、内存屏障等概念。了解JVM内存模型有助于理解多线程并发编程中内存可见性、原子性、有序性等问题。

5. 在实现生产者-消费者模型时,可以使用锁机制来保证生产者和消费者之间的线程安全。在代码中要注意对共享资源的加锁和解锁操作,避免出现数据竞争和不一致的情况。在提交代码到GitHub时,可以在注释中说明所使用的锁优化策略,以便他人阅读和理解代码的逻辑。

二、生产者-消费者模型实现

完整代码示例:

import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ProducerConsumerDemo {
    private static final int MAX_CAPACITY = 5; // 缓冲区最大容量
    private final LinkedList<Integer> buffer = new LinkedList<>();
    private final ReentrantLock lock = new ReentrantLock(); // 显式锁
    private final Condition notFull = lock.newCondition(); // 缓冲区未满条件
    private final Condition notEmpty = lock.newCondition(); // 缓冲区非空条件

    // 生产者线程
    class Producer implements Runnable {
        private int itemCount = 0;

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                lock.lock(); // 获取锁
                try {
                    while (buffer.size() == MAX_CAPACITY) { // 检查缓冲区是否已满
                        System.out.println("缓冲区已满,生产者等待...");
                        notFull.await(); // 释放锁并等待
                    }
                    int item = ++itemCount;
                    buffer.add(item);
                    System.out.println("生产: " + item + " | 缓冲区大小: " + buffer.size());
                    notEmpty.signalAll(); // 唤醒消费者
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    lock.unlock(); // 释放锁
                }
                try {
                    Thread.sleep(500); // 模拟生产耗时
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    // 消费者线程
    class Consumer implements Runnable {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                lock.lock();
                try {
                    while (buffer.isEmpty()) { // 检查缓冲区是否为空
                        System.out.println("缓冲区为空,消费者等待...");
                        notEmpty.await(); // 释放锁并等待
                    }
                    int item = buffer.removeFirst();
                    System.out.println("消费: " + item + " | 缓冲区大小: " + buffer.size());
                    notFull.signalAll(); // 唤醒生产者
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    lock.unlock();
                }
                try {
                    Thread.sleep(1000); // 模拟消费耗时
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ProducerConsumerDemo demo = new ProducerConsumerDemo();
        // 创建多个生产者和消费者
        Thread producer1 = new Thread(demo.new Producer(), "生产者1");
        Thread producer2 = new Thread(demo.new Producer(), "生产者2");
        Thread consumer1 = new Thread(demo.new Consumer(), "消费者1");
        Thread consumer2 = new Thread(demo.new Consumer(), "消费者2");

        producer1.start();
        producer2.start();
        consumer1.start();
        consumer2.start();

        Thread.sleep(10_000); // 运行10秒后终止
        producer1.interrupt();
        producer2.interrupt();
        consumer1.interrupt();
        consumer2.interrupt();
    }
}

运行演示

1.初始阶段

  • 生产者快速填充缓冲区至最大值(5),输出类似

生产: 1 | 缓冲区大小: 1  
生产: 2 | 缓冲区大小: 2  
...  
生产: 5 | 缓冲区大小: 5  
缓冲区已满,生产者等待...

  • 消费者开始消费,缓冲区逐渐减少

2.动态平衡阶段

  • 生产者和消费者交替运行。例如:

消费: 3 | 缓冲区大小: 4  
生产: 6 | 缓冲区大小: 5  
缓冲区已满,生产者等待...

3.终止阶段

  • 主线程休眠10秒后中断所有线程,程序结束。

 

代码解析与设计要点

1.核心组件

  •  缓冲区(buffer):

     使用 LinkedList 作为共享资源,实现先进先出(FIFO)队列。容量限制通过Max_CAPACITY控制。

  • 显示锁 (ReentrantLock):

替代synchronized,提供更灵活的锁控制,支持中断和超时。

  • 条件变量(Condition):

*notFull:生产者等待缓冲区未满条件。

* notEmpty:消费者等待缓冲区非空条件。

替代 wait/notify,允许精确唤醒特定类型线程

2.生产者逻辑

1.获取锁:同生产者

2.检查缓冲区状态

若缓冲区已满,调用 notFull.await() 释放锁并进入等待队列

3.生产数据

等待数据到缓冲区,并打印生产信息。

4.唤醒消费者

notEmpty.signalAll() 通知消费者可以消费

5.释放锁

finally 块保证锁一定被释放,避免死锁

6.模拟耗时操作

Thread.sleep(500)降低生产者速度,平衡生产消费速率

3.消费者逻辑

1.获取锁:同生产者

2.检查缓冲区状态

  • 若缓冲区为空,调用 notEmpty.await() 等待数据

3.消费数据

  • 移除并处理缓冲区头部数据,打印消费信息

4.唤醒生产者

  • notFull.signalAll() 通知消费者可以继续生产

5.模拟耗时操作

  • Thread.sleep(1000) 模拟消费时间,通常比生产者更慢

4.主程序控制

  • 多线程启动:支持多个生产者和消费者并发运行
  • 优雅终止:通过 interrupt() 中断线程,结合 Thread.sleep 控制运行时间

任务验收:

1.生产者-消费者模型验收:

  • 使用ReetrantLock+Condition实现
  • 缓冲区大小限制(防止内存溢出)
  • 包含公平锁配置
  • 异常处理机制(tryLock+超时机制)

2.锁机制验收

  • 功能特性对比(公平性、条件变量等)
  • 性能对比说明(不同JDK版本差异)
  • 使用场景建议(简单VS复杂场景)

三、synchronized vs ReentrantLock对比分析

1. 实现机制对比


| 特性              | synchronized          | ReentrantLock         |
|-------------------|-----------------------|-----------------------|
| 锁获取方式        | JVM隐式管理           | API显式调用           |
| 中断支持          | 不支持                | lockInterruptibly()   |
| 公平锁            | 非公平                | 可配置公平策略        |
| 条件变量          | 单个监视器            | 多Condition对象       |

2. 使用场景建议

  • - **synchronized**:简单同步场景、自动资源管理

  • - **ReentrantLock**:需要细粒度控制、超时机制、公平锁等高级功能

四、关键设计优化

 

  • 避免虚假唤醒:使用  while  而非  if  检查条件,确保线程被唤醒后再次验证状态。

  • 精确唤醒:Condition  的  signalAll()  仅唤醒对应条件的线程(如消费者唤醒生产者),减少无效竞争。 

  • 中断处理:在  catch (InterruptedException)  中重置中断标志,确保上层逻辑能感知中断。

 
 
扩展对比
 
 BlockingQueue  简化版:
使用  ArrayBlockingQueue  可替代手动同步逻辑(如  put()  和  take()  方法自动阻塞),但隐藏了底层锁机制。
 
 synchronized  基础版:
代码更简洁,但无法区分唤醒条件,可能导致性能损耗。
 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值