java线程池将数据减少为负数,即使条件是在0处停止

lndjwyie  于 2021-06-26  发布在  Java
关注(0)|答案(1)|浏览(328)

处理器类-

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个项目,条件是如果库存为空,线程将处于空闲状态。
此程序不是线程安全的,并且最后的库存计数变为负数。
如何使这个程序线程安全?

0g0grzrc

0g0grzrc1#

如何使这个程序线程安全?
首先标识线程间的共享状态;可以确定四个项目,即: stock , lock1 , lock2 ,和 lock3 . 这些字段是静态的,因此在线程之间共享。现在,寻找涉及共享状态的潜在竞争条件。
这些字段只被读取吗?如果是,则不存在竞赛条件。是否以某种方式修改了这些字段?是的,那么您需要确保在访问这些字段时互斥。
田野 lock1 , lock2 ,和 lock3 使用方法如下:

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

因此,没有种族条件。菲尔德怎么样 stock ?! 我们在方法中有一个read isStockEmpty (即。, stock == 0 ),方法中的读取和写入 decreaseStock (即。, stock--; ),最后在方法中读取另一个 getStockCount (即。, value = stock; ). 因此,有可能由多个线程并行执行多个读写操作,因此,必须确保该字段上的互斥。您已经添加了同步部分,但是,您需要使用相同的锁来确保线程不会同时读写。
从oracle教程中可以看到:
每个对象都有一个与之相关联的内在锁。按照约定,需要独占和一致地访问对象字段的线程必须在访问对象字段之前获取对象的内部锁,然后在处理完这些字段后释放内部锁。线程在获取锁和释放锁之间拥有内在锁。只要一个线程拥有一个内在锁,其他线程就不能获得相同的锁。另一个线程将在尝试获取锁时阻塞。
因此,与其使用三个不同的对象进行同步以确保相同数据的互斥,不如只使用一个对象,这样您的代码将如下所示:

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 继续呼叫 decreaseStock() ,同时(之前 Thread 1 电话 synchronized (stock_lock) ) Thread 2 同时呼叫 !isStockEmpty() 这也将评估 true . Thread 1 执行操作 stock-- ,使库存为0,因为 Thread 2 已经在 if (!isStockEmpty()) , Thread 2 也将执行 stock-- ,使库存=-1。此外,您与 getStockCount() 在医院里打过电话 doWork 方法也是。解决方案是同步整个代码块,即:

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

现在,因为 isStockEmpty , decreaseStock ,和 getStockCount 在中调用的所有私有方法 synchronized (stock_lock)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) 我们将返回值保存到一个局部变量(线程专用)中,并使用该值而不是 getStockCount . 尽管如此 stock 变量理论上可以得到负值。但这些值不会显示出来。

相关问题