处理器类-
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个项目,条件是如果库存为空,线程将处于空闲状态。
此程序不是线程安全的,并且最后的库存计数变为负数。
如何使这个程序线程安全?
1条答案
按热度按时间0g0grzrc1#
如何使这个程序线程安全?
首先标识线程间的共享状态;可以确定四个项目,即:
stock
,lock1
,lock2
,和lock3
. 这些字段是静态的,因此在线程之间共享。现在,寻找涉及共享状态的潜在竞争条件。这些字段只被读取吗?如果是,则不存在竞赛条件。是否以某种方式修改了这些字段?是的,那么您需要确保在访问这些字段时互斥。
田野
lock1
,lock2
,和lock3
使用方法如下:因此,没有种族条件。菲尔德怎么样
stock
?! 我们在方法中有一个readisStockEmpty
(即。,stock == 0
),方法中的读取和写入decreaseStock
(即。,stock--;
),最后在方法中读取另一个getStockCount
(即。,value = stock;
). 因此,有可能由多个线程并行执行多个读写操作,因此,必须确保该字段上的互斥。您已经添加了同步部分,但是,您需要使用相同的锁来确保线程不会同时读写。从oracle教程中可以看到:
每个对象都有一个与之相关联的内在锁。按照约定,需要独占和一致地访问对象字段的线程必须在访问对象字段之前获取对象的内部锁,然后在处理完这些字段后释放内部锁。线程在获取锁和释放锁之间拥有内在锁。只要一个线程拥有一个内在锁,其他线程就不能获得相同的锁。另一个线程将在尝试获取锁时阻塞。
因此,与其使用三个不同的对象进行同步以确保相同数据的互斥,不如只使用一个对象,这样您的代码将如下所示:
程序现在是线程安全的吗?!,差不多,但那里仍然有一个鬼鬼祟祟的竞赛条件,即:
即使两种方法分开
isStockEmpty
以及decreaseStock
是线程安全的,当它们一起调用时没有,为什么?因为检查库存是否为空和减少库存的整个操作都需要按顺序进行。否则,可能发生以下竞态情况:田野
stock
是1,Thread 1
同步于isStockEmpty
检查是否为空!isStockEmpty()
计算结果为true
,Thread 1
继续呼叫decreaseStock()
,同时(之前Thread 1
电话synchronized (stock_lock)
)Thread 2
同时呼叫!isStockEmpty()
这也将评估true
.Thread 1
执行操作stock--
,使库存为0,因为Thread 2
已经在if (!isStockEmpty())
,Thread 2
也将执行stock--
,使库存=-1。此外,您与getStockCount()
在医院里打过电话doWork
方法也是。解决方案是同步整个代码块,即:现在,因为
isStockEmpty
,decreaseStock
,和getStockCount
在中调用的所有私有方法synchronized (stock_lock)
的doWork
方法,我们实际上可以从这些方法中删除我们在开始时添加的同步。整个代码如下所示:现在在一个真实的例子中,如果你像那样同步整个程序,你也可以按顺序执行代码。
或者,您可以通过使用
AtomicInteger
对于stock
字段变量:没有竞争条件,因为1)
getAndDecrement
是原子化的;2) 我们将返回值保存到一个局部变量(线程专用)中,并使用该值而不是getStockCount
. 尽管如此stock
变量理论上可以得到负值。但这些值不会显示出来。