Java多线程并发控制实战:同步、生产者消费者与Callable应用
本文通过数字加减、生产搬运电脑及竞拍抢答三个综合案例,深入解析Java多线程中的`synchronized`关键字、`wait()`与`notifyAll()`的协作机制,以及`Callable`接口在异步任务中的应用。旨在帮助开发者理解线程间的资源竞争、锁释放与唤醒流程,掌握高并发环境下的基础编程技巧。
数字加减:互斥同步与等待唤醒
此案例的核心在于解决两个加法线程与两个减法线程对共享资源 `num` 的并发访问问题。关键在于保证数据变化的原子性与一致性:执行一次加操作后必须紧接着执行减操作(或反之),使得最终结果仅在 0、-1、1 之间循环波动。
实现方案采用类级别的 synchronized 方法配合布尔标志位 flag 进行状态切换:
- 加法操作 (
add()):若标志位为false(表示正在进行减法),线程调用wait()释放锁并等待;否则执行加1操作,设置flag=false,调用notifyAll()唤醒所有等待线程。 - 减法操作 (
sub()):逻辑相反,若标志位为true则等待;执行减1后设置flag=true并唤醒其他线程。
public class ThreadDemo {
public static void main(String[] args) throws Exception {
Resource res = new Resource();
AddThread at = new AddThread(res);
SubThread st = new SubThread(res);
// 启动两个加法线程和两个减法线程
new Thread(at, "加法线程A:").start();
new Thread(at, "加法线程B:").start();
new Thread(st, "减法线程X:").start();
new Thread(st, "减法线程Y:").start();
}
}
class AddThread implements Runnable {
private Resource resource;
public AddThread(Resource resource) { this.resource = resource; }
@Override
public void run() {
for (int x = 0; x < 50; x++) {
try { resource.add(); } catch (Exception e) { e.printStackTrace(); }
}
}
}
class SubThread implements Runnable {
private Resource resource;
public SubThread(Resource resource) { this.resource = resource; }
@Override
public void run() {
for (int x = 0; x < 50; x++) {
try { resource.sub(); } catch (Exception e) { e.printStackTrace(); }
}
}
}
class Resource {
private int num = 0;
private boolean flag = true; // true:可进行加法, false:可进行减法
public synchronized void add() throws Exception {
if (!this.flag) { super.wait(); } // 等待减法完成
Thread.sleep(100); // 模拟耗时
this.num++;
System.out.println("加法操作-" + Thread.currentThread().getName() + "num=" + this.num);
this.flag = false; // 切换状态
super.notifyAll(); // 唤醒其他等待线程
}
public synchronized void sub() throws Exception {
if (this.flag) { super.wait(); } // 等待加法完成
Thread.sleep(200); // 模拟耗时
this.num--;
System.out.println("减法操作-" + Thread.currentThread().getName() + "num=" + this.num);
this.flag = true;
super.notifyAll();
}
}
生产电脑:经典生产者-消费者模型
该场景要求“生产一台,搬走一台”,涉及资源队列为空时等待生产,或资源满时等待消费的情况。这是典型的生产者-消费者(Producer-Consumer)设计模式。
解决方案如下:
- 状态检查:通过
Computer computer引用是否为null来判断库存状态。 - 生产者 (
make()):若computer != null,说明已有产品未取走,生产者调用wait()阻塞;否则创建新对象,打印信息,并将引用置为null(逻辑上已放入仓库等待消费,但在本简化模型中,通过get消费后才设为null,此处代码逻辑是:生产后notify,消费者拿到对象后消费并设为null,这里需要仔细对照代码:
注意:下方代码中,生产者生产后将this.computer赋值,然后notifyAll。消费者获取非null对象后,打印并设为null,再notifyAll。这种机制确保同一时间只有一个产品在流通。
public class ThreadDemo {
public static void main(String[] args) throws Exception {
Resource res = new Resource();
new Thread(new Producer(res)).start();
new Thread(new Consumer(res)).start();
}
}
class Producer implements Runnable {
private Resource resource;
public Producer(Resource resource) { this.resource = resource; }
@Override
public void run() {
for (int x = 0; x < 50; x++) { this.resource.make(); }
}
}
class Consumer implements Runnable {
private Resource resource;
public Consumer(Resource resource) { this.resource = resource; }
@Override
public void run() {
for (int x = 0; x < 50; x++) { this.resource.get(); }
}
}
class Resource {
private Computer computer;
public synchronized void make() {
if (this.computer != null) { // 还有存货,等待消费
try { super.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
this.computer = new Computer("小米电脑", 1.1);
System.out.println("生产电脑" + this.computer);
super.notifyAll();
}
public synchronized void get() {
if (this.computer == null) { // 无货,等待生产
try { super.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("取走电脑" + this.computer);
this.computer = null; // 清空货物
super.notifyAll();
}
}
class Computer {
private static int count = 0;
private String name;
private double price;
public Computer(String name, double price) {
this.name = name; this.price = price; count++;
}
public String toString() {
return "第" + count + "台电脑 名字:" + this.name + "、价值:" + this.price;
}
}
竞争抢答:基于Callable的异步结果获取
当多线程操作需要返回具体结果(如谁抢答成功)时,继承 Thread 或实现 Runnable 无法直接获取返回值。此时应采用 java.util.concurrent.Callable 接口配合 FutureTask 进行处理。
在此案例中,三个线程竞争修改 flag 标志位。由于 call() 内部使用了 synchronized 块保护逻辑,确保了只有第一个到达的线程能修改 flag 为 true 并返回成功,后续线程将返回失败。
package java线程;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) throws Exception {
Mythread mt = new Mythread();
// FutureTask 包装 Callable 任务,可获取执行结果
FutureTask<String> taskA = new FutureTask<>(mt);
FutureTask<String> taskB = new FutureTask<>(mt);
FutureTask<String> taskC = new FutureTask<>(mt);
new Thread(taskA, "竞赛者A").start();
new Thread(taskB, "竞赛者B").start();
new Thread(taskC, "竞赛者C").start();
// 主线程阻塞直到任务执行完毕并获取返回值
System.out.println(taskA.get());
System.out.println(taskB.get());
System.out.println(taskC.get());
}
}
class Mythread implements Callable<String> {
private boolean flag = false;
@Override
public String call() throws Exception {
synchronized (this) {
if (!this.flag) {
this.flag = true;
return Thread.currentThread().getName() + "抢答成功";
} else {
return Thread.currentThread().getName() + "抢答失败";
}
}
}
}
以上就是Java多线程并发控制在不同场景下应用与实现的详细内容,更多关于Java多线程学习资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













