处理器类-
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 个项目,条件是如果库存为空,线程将闲置。
此程序不是线程安全的,最后的库存数量变为负数。
如何使这个程序线程安全?
如何使这个程序线程安全?
首先标识线程之间的共享状态;可以识别四个项目,即:stock
、lock1
、lock2
和lock3
。这些字段是静态的,因此在线程之间共享。现在查找涉及该共享状态的潜在争用条件。
这些字段是否仅被读取? 如果是,则没有争用条件。这些字段是否以某种方式被修改?是的,那么您需要确保对这些字段的访问相互排斥。
字段lock1
、lock2
和lock3
的使用方式如下:
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();
即使方法isStockEmpty
和decreaseStock
分别是线程安全的,当它们一起调用时,为什么没有? 因为检查库存是否为空并减少的整个操作需要按顺序完成。否则,可能会发生以下争用条件:
字段stock
为 1,Thread 1
同步isStockEmpty
检查是否为空,!isStockEmpty()
计算结果为true
,Thread 1
同时(在Thread 1
调用synchronized (stock_lock)
之前)继续调用decreaseStock()
,Thread 2
也调用!isStockEmpty()
,这也将计算为true
。Thread 1
执行stock--
的操作,使 stock = 0,并且因为Thread 2
已经在if (!isStockEmpty())
的块包装内,Thread 2
也会执行stock--
,使 stock = -1。此外,您与doWork
方法中调用的getStockCount()
也有类似的争用条件。解决方案是同步整个代码块,即:
private void doWork() {
synchronized (stock_lock) {
....
}
}
现在,由于isStockEmpty
、decreaseStock
和getStockCount
都是在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
变量理论上可以得到负值。但不会显示这些值。