线程池将数据减少到负数,即使条件为停止在零



处理器类-

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 stockn" +
"Items remaining in stock: " + getStockCount());
snooze(2000);
} else {
System.out.println("Stock is empty, " + Thread.currentThread().getName() + "is idlen" +
"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 个项目,条件是如果库存为空,线程将闲置。

此程序不是线程安全的,最后的库存数量变为负数。

如何使这个程序线程安全?

如何使这个程序线程安全?

首先标识线程之间的共享状态;可以识别四个项目,即:stocklock1lock2lock3。这些字段是静态的,因此在线程之间共享。现在查找涉及该共享状态的潜在争用条件

这些字段是否仅被读取? 如果是,则没有争用条件。这些字段是否以某种方式被修改?是的,那么您需要确保对这些字段的访问相互排斥。

字段lock1lock2lock3的使用方式如下:

synchronized (lock1) { ... }
... 
synchronized (lock2) { ... }
...
synchronized (lock3) { ... }

因此,那里没有竞争条件。现场stock怎么样?!我们在方法isStockEmpty(stock == 0)内有一个读取,在方法decreaseStock内有一个读取和写入(stock--;),最后在方法getStockCount内进行另一个读取(即,value = stock;)。因此,多个线程可能会并行发生多个读取和写入,因此必须确保在该字段上相互排斥。您已经添加了同步部分,但是,您需要使用相同的锁来确保线程不会同时读取和写入。

从预言机教程中可以阅读:

每个对象都有一个与之关联的内在锁。按照惯例, 需要对对象的独占和一致访问的线程 字段在访问之前必须获取对象的固有锁 它们,然后在处理完它们后释放固有锁。一个 据说线程在它拥有的时间之间拥有内在锁 获取了锁并释放了锁。只要线程拥有 内部锁,没有其他线程可以获取相同的锁。另一个 线程在尝试获取锁时将阻塞。

因此,与其使用三个不同的对象进行同步以确保相互排除相同的数据,不如让我们只使用一个对象,因此您的代码如下所示:

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 1同步isStockEmpty检查是否为空!isStockEmpty()计算结果为trueThread 1同时(在Thread 1调用synchronized (stock_lock)之前)继续调用decreaseStock()Thread 2也调用!isStockEmpty(),这也将计算为trueThread 1执行stock--的操作,使 stock = 0,并且因为Thread 2已经在if (!isStockEmpty())的块包装内,Thread 2也会执行stock--,使 stock = -1。此外,您与doWork方法中调用的getStockCount()也有类似的争用条件。解决方案是同步整个代码块,即:

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

现在,由于isStockEmptydecreaseStockgetStockCount都是在doWork方法的synchronized (stock_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() { return stock == 0; }

private void decreaseStock() { stock--;}

private int getStockCount() {  return stock;}

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

@Override
public void run() {
doWork();
}
}

现在在一个现实生活中的例子中,如果你像这样同步整个程序,你不妨按顺序执行代码。

或者,您可以通过对stock字段变量使用AtomicInteger来使当前程序线程安全:

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 stockn" +
"Items remaining in stock: " + value);
snooze(2000);
} else {
System.out.println("Stock is empty, " + Thread.currentThread().getName() + "is idlen" +
"Items remaining in stock: " + value);
snooze(2000);
}
}

没有竞争条件,因为 1)getAndDecrement是通过原子方式完成的;2)我们将返回值保存到局部变量(线程专用)中,并使用该值而不是getStockCount。尽管如此,stock变量理论上可以得到负值。但不会显示这些值。

最新更新