Java多线程同步:生产者模型实现与解析
一、题目代码分析
以下是需要补全的Java多线程代码,核心功能是模拟两个工人(线程)协作生产产品,通过同步机制确保生产数量的正确性。代码需要解决多线程并发访问共享资源时可能出现的数据不一致问题:
public class Java_2 {
public static void main(String[] args){
Workshop shop;
shop = new Workshop();
// 创建两个线程,共享同一个Workshop实例
Thread w1 = new Thread(shop);
Thread w2 = new Thread(shop);
w1.setName("Worker A");
w2.setName("Worker B");
w1.start(); // 启动线程1
w2.start(); // 启动线程2
}
}
//**********Found**********
class Workshop _________________{
//**********Found**********
_________ int products = 0; // 产品数量(共享资源)
//**********Found**********
public void ______( ){
for (int i = 0; i<10; i++){
produce(); // 调用生产方法
//**********Found**********
_______{
Thread.sleep(2); // 模拟生产耗时
//**********Found**********
}_______(Exception e)
}
}
public void produce(){
//**********Found**********
synchronized(_______){ // 同步代码块,确保线程安全
if(products <10){
products++; // 增加产品数量
System.out.print(Thread.currentThread().getName());
System.out.println( " adds one product, and totally " + products +" products produced.");
}
}
}
}
二、空白处逐一解析
要实现多线程安全的产品生产模拟,需围绕“线程接口实现”“共享资源可见性”“同步机制”“异常处理”四个核心目标,逐个突破空白:
1. 第一个空白:class Workshop _________________{
- 解析:
Workshop
类的实例作为Thread
构造参数,说明它必须实现Runnable
接口(线程执行体接口),否则无法作为线程任务。 - 答案:
implements Runnable
2. 第二个空白:_________ int products = 0;
- 解析:
products
是多线程共享的产品数量变量,需用volatile
修饰以保证内存可见性(一个线程修改后,其他线程能立即看到最新值),避免线程缓存导致的数据不一致。 - 答案:
volatile
3. 第三个空白:public void ______( ){
- 解析:
Runnable
接口强制要求实现run()
方法,作为线程的执行入口(线程启动后自动调用run()
方法)。 - 答案:
run
4. 第四个空白:_______{
- 解析:
Thread.sleep(2)
可能抛出InterruptedException
(受查异常),必须用try
块包裹可能抛出异常的代码。 - 答案:
try
5. 第五个空白:}_______(Exception e)
- 解析:与
try
块搭配,用catch
捕获try
块中抛出的Exception
异常,避免程序崩溃。 - 答案:
catch
6. 第六个空白:synchronized(_______){
- 解析:
synchronized
同步代码块需要指定锁对象,确保同一时间只有一个线程执行该代码块。此处使用this
(当前Workshop
实例)作为锁,保证两个线程竞争同一把锁。 - 答案:
this
三、完整正确代码
public class Java_2 {
public static void main(String[] args){
Workshop shop;
shop = new Workshop();
// 创建两个线程,共享同一个Workshop实例(关键:共享资源)
Thread w1 = new Thread(shop);
Thread w2 = new Thread(shop);
w1.setName("Worker A");
w2.setName("Worker B");
w1.start(); // 启动线程1
w2.start(); // 启动线程2
}
}
// Workshop类实现Runnable接口,作为线程执行体
class Workshop implements Runnable {
// 共享资源:产品数量,用volatile保证内存可见性
volatile int products = 0;
// 线程执行入口:实现Runnable接口的run()方法
public void run() {
for (int i = 0; i < 10; i++) { // 每个工人尝试生产10次
produce(); // 调用生产方法(同步方法)
try { // 捕获sleep可能抛出的中断异常
Thread.sleep(2); // 模拟生产耗时(毫秒)
} catch (InterruptedException e) {
// 处理中断异常(实际开发中可记录日志)
e.printStackTrace();
}
}
}
// 生产产品的方法(需要同步控制)
public void produce() {
// 同步代码块:使用this作为锁对象,确保线程安全
synchronized(this) {
// 仅当产品数量小于10时才生产(避免超量生产)
if(products < 10) {
products++; // 产品数量+1
// 输出当前线程名称和生产后总数量
System.out.print(Thread.currentThread().getName());
System.out.println(" adds one product, and totally " + products + " products produced.");
}
}
}
}
优化说明:
- 将异常类型细化为
InterruptedException
(Thread.sleep()
实际抛出的异常),避免过度捕获;- 增加异常处理逻辑(
e.printStackTrace()
),便于调试;- 调整代码格式(如空格、换行),提升可读性。
四、代码运行示例
Worker A adds one product, and totally 1 products produced.
Worker B adds one product, and totally 2 products produced.
Worker A adds one product, and totally 3 products produced.
Worker B adds one product, and totally 4 products produced.
Worker A adds one product, and totally 5 products produced.
Worker B adds one product, and totally 6 products produced.
Worker A adds one product, and totally 7 products produced.
Worker B adds one product, and totally 8 products produced.
Worker A adds one product, and totally 9 products produced.
Worker B adds one product, and totally 10 products produced.
说明:由于线程调度的不确定性,A和B的输出顺序可能不同,但最终总产品数一定是10(同步机制保证)。
五、核心知识点总结
通过这个多线程生产模拟实例,可系统掌握Java多线程同步的3个核心技术点:
1. 线程创建方式:实现Runnable接口
本例使用“实现Runnable接口”的方式创建线程,优势是:
- 避免单继承限制(一个类可同时实现
Runnable
和其他接口); - 适合多线程共享同一资源(多个线程可共享一个
Runnable
实例)。
核心步骤:
- 类实现
Runnable
接口; - 重写
run()
方法(线程执行体); - 创建
Thread
对象,传入Runnable
实例; - 调用
start()
方法启动线程(底层调用run()
)。
2. 共享资源可见性:volatile关键字
volatile int products = 0
中的volatile
作用:
- 禁止指令重排序:确保
products
的读写操作按顺序执行; - 内存可见性:一个线程修改
products
后,其他线程能立即看到最新值(避免线程缓存导致的“脏读”)。
注意:volatile
仅保证可见性,不保证原子性(如products++
是“读取-修改-写入”三步操作,仍需同步机制)。
3. 线程同步:synchronized关键字
synchronized(this) { ... }
同步代码块的作用:
- 互斥性:同一时间只有一个线程能进入同步块(通过锁对象实现,本例锁对象是
this
); - 原子性:确保
if(products <10)
和products++
作为整体执行,避免“超量生产”(如两个线程同时判断products=9
,导致最终products=11
)。
同步原理:
- 线程进入同步块前需获取锁对象的“监视器锁(monitor)”;
- 其他线程若未获取锁,会进入阻塞状态,直到锁被释放;
- 锁释放时,会将工作内存中的修改刷新到主内存(保证可见性)。
六、未同步的风险与同步机制的必要性
如果去掉synchronized
同步块,可能出现以下问题:
- 超量生产:两个线程同时判断
products=9
,都执行products++
,最终products=11
(超出预期的10); - 计数错误:
products++
是非原子操作,可能导致“丢失更新”(如两个线程同时读取products=5
,都加1后写入,结果仍为6而非7); - 输出混乱:
System.out.print()
和System.out.println()
可能被交叉执行,导致输出内容错乱。
同步机制(如synchronized
)通过“互斥访问”解决了这些问题,是多线程安全的基础。
七、拓展与优化建议
本例可从以下方向扩展,深入理解多线程同步:
1. 使用同步方法替代同步代码块
将produce()
方法声明为同步方法(等价于 synchronized(this)
):
// 同步方法:锁对象是this
public synchronized void produce() {
if(products < 10) {
products++;
// ...输出逻辑
}
}
2. 使用显式锁(ReentrantLock)
JDK 1.5+提供的ReentrantLock
比synchronized
更灵活(支持超时获取锁、可中断锁等):
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Workshop implements Runnable {
private Lock lock = new ReentrantLock(); // 显式锁
volatile int products = 0;
public void produce() {
lock.lock(); // 获取锁
try {
if(products < 10) {
products++;
// ...输出逻辑
}
} finally {
lock.unlock(); // 确保锁释放(必须放在finally中)
}
}
// ...其他代码
}
3. 限制总生产次数
当前代码中,每个线程尝试生产10次,但实际只需生产10个产品。可使用wait()
和notify()
优化,避免无效尝试:
public void produce() throws InterruptedException {
synchronized(this) {
while(products >= 10) { // 用while避免虚假唤醒
wait(); // 产品已满,当前线程等待
}
products++;
System.out.println(...);
notifyAll(); // 唤醒其他等待线程
}
}
八、总结
本例通过两个工人协作生产产品的场景,展示了Java多线程编程的核心问题:共享资源的并发访问控制。Runnable
接口实现了线程任务的定义,volatile
保证了共享变量的可见性,synchronized
通过互斥锁解决了原子性问题,三者结合实现了线程安全的生产过程。
多线程同步是Java并发编程的基础,理解synchronized
的工作原理、volatile
的适用场景以及线程创建方式,对于开发高性能、线程安全的应用程序至关重要。实际开发中,需根据业务场景选择合适的同步机制(如synchronized
适合简单场景,ReentrantLock
适合复杂场景),在性能和安全性之间找到平衡。