gpt4 book ai didi

java - 即使条件停止为零,线程池也会将数据减少到负数

转载 作者:行者123 更新时间:2023-12-03 12:58:19 25 4
gpt4 key购买 nike

处理器类 -

public class Processor extends Thread {

private static int stock = 10;

private static Object lock1 = new Object();
private static Object lock2 = new Object();
private static Object lock3 = new Object();

private void snooze(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

private boolean isStockEmpty() {
boolean value;
synchronized (lock1) {
if (stock == 0) {
value = true;
} else {
value = false;
}
}
return value;
}

private void decreaseStock() {
synchronized (lock2) {
stock--;
}
}

private int getStockCount() {
int value;
synchronized (lock3) {
value = stock;
}
return value;
}

private void doWork() {
if (!isStockEmpty()) {
decreaseStock();
System.out.println(Thread.currentThread().getName() + " takes 1 item from stock\n" +
"Items remaining in stock: " + getStockCount());
snooze(2000);
} else {
System.out.println("Stock is empty, " + Thread.currentThread().getName() + "is idle\n" +
"Items remaining in stock: " + getStockCount());
snooze(2000);
}
}

@Override
public void run() {
doWork();
}
}
主要方法——
public class Main {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 12; i++) {
executorService.submit(new Processor());
}
executorService.shutdown();
System.out.println("All tasks successfully submitted");
executorService.awaitTermination(1, TimeUnit.DAYS);
System.out.println("All tasks completed successfully");
}
}
在这里,我正在创建一个由三个线程组成的线程池,并为它们分配一个作业,以从 10 个库存中获取 12 个项目,条件是如果库存变空,线程将处于空闲状态。
该程序不是线程安全的,最后的库存计数变为负数。
如何使这个程序线程安全?

最佳答案

How to make this program thread safe?


首先标识线程间的共享状态;可以识别四个项目,即: stock , lock1 , lock2 , 和 lock3 .这些字段是静态的, hence shared among threads .现在寻找涉及该共享状态的潜在竞争条件。
这些字段是否仅被读取?如果是,则不存在竞争条件。这些字段是否以某种方式被修改?是的,那么您需要确保 mutual exclusion关于对这些领域的访问。
字段 lock1 , lock2 , 和 lock3使用如下:
synchronized (lock1) { ... }
...
synchronized (lock2) { ... }
...
synchronized (lock3) { ... }
因此,那里没有竞争条件。字段如何 stock ?!我们在方法 isStockEmpty 中进行了读取(即 stock == 0 ),方法中的读取和写入 decreaseStock (即 stock--; ),最后在 getStockCount 方法中再次读取(即 value = stock; )。因此,多个线程有可能并行发生多个读取和写入,因此必须确保该字段的互斥。您已经添加了同步部分,但是,您需要使用相同的锁来确保线程不会并发读取和写入。
来自 oracle tutorial可以阅读:

Every object has an intrinsic lock associated with it. By convention,a thread that needs exclusive and consistent access to an object'sfields has to acquire the object's intrinsic lock before accessingthem, and then release the intrinsic lock when it's done with them. Athread is said to own the intrinsic lock between the time it hasacquired the lock and released the lock. As long as a thread owns anintrinsic lock, no other thread can acquire the same lock. The otherthread will block when it attempts to acquire the lock.


因此,不要使用三个不同的对象进行同步以确保相同数据的互斥,让我们只使用一个,因此您的代码如下所示:
public class Processor extends Thread {

private static final Object stock_lock = new Object();

@GuardedBy("lock")
private static int stock = 10;

private void snooze(long millis) { ...}

private boolean isStockEmpty() {
synchronized (stock_lock) {
return stock == 0;
}
}

private void decreaseStock() {
synchronized (stock_lock) {
stock--;
}
}

private int getStockCount() {
synchronized (stock_lock) {
return stock;
}
}

...
}
该程序现在是线程安全的吗?!,几乎但是那里仍然存在一个偷偷摸摸的竞争条件,即:
if (!isStockEmpty()) {
decreaseStock();
即使两种方法分开 isStockEmptydecreaseStock是线程安全的,当它们一起调用时没有,为什么?因为检查库存是否为空和减少它的整个操作需要按顺序进行。否则,可能会发生以下竞争条件:
字段 stock是 1, Thread 1isStockEmpty 中同步检查是否为空和 !isStockEmpty()计算结果为 true , Thread 1继续调用 decreaseStock() ,同时(在 Thread 1 调用 synchronized (stock_lock) 之前) Thread 2也调用 !isStockEmpty()这也将评估为 true . Thread 1执行操作 stock-- ,使库存 = 0,并且因为 Thread 2已经在 if (!isStockEmpty()) 的 block 包装内, Thread 2还将执行 stock-- ,使库存 = -1。此外,您与 getStockCount() 有类似的竞争条件。在 doWork 内部调用方法也是。解决方案是同步整个代码块,即:
private void doWork() {
synchronized (stock_lock) {
....
}
}
现在,因为 isStockEmpty , decreaseStock , 和 getStockCount都是在 synchronized (stock_lock) 内调用的私有(private)方法的 doWork方法,我们实际上可以从这些方法中删除我们在开始时添加的同步。所以整个代码看起来像这样:
public class Processor extends Thread {
private static final Object stock_lock = new Object();

@GuardedBy("lock")
private static int stock = 10;


private void snooze(long millis) {...}

private boolean isStockEmpty() { return stock == 0; }

private void decreaseStock() { stock--;}

private int getStockCount() { return stock;}

private void doWork() {
synchronized (stock_lock) {
....
}
}

@Override
public void run() {
doWork();
}
}
现在在一个真实的例子中,如果你像这样同步整个程序,你不妨只按顺序执行代码。
或者,您可以使用 AtomicInteger 使当前程序线程安全。对于 stock字段变量:
private static AtomicInteger stock = new AtomicInteger(10);

private void snooze(long millis) {... }
private void doWork() {
int value = stock.getAndDecrement();
if (value != 0) {
System.out.println(Thread.currentThread().getName() + " takes 1 item from stock\n" +
"Items remaining in stock: " + value);
snooze(2000);
} else {
System.out.println("Stock is empty, " + Thread.currentThread().getName() + "is idle\n" +
"Items remaining in stock: " + value);
snooze(2000);
}
}
没有竞争条件,因为 1) getAndDecrement以原子方式完成; 2)我们将返回值保存到一个局部变量(线程私有(private))中,并使用该值而不是 getStockCount .尽管如此, stock变量理论上可以得到负值。但不会显示这些值。

关于java - 即使条件停止为零,线程池也会将数据减少到负数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65603889/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com