关于并发编程中临界资源的问题

临界资源: 指并发环境中多个进程/线程共享的资源.

在并发编程中对临界资源的处理不当, 往往会导致数据不一致的问题. 例如有一份账户数据 account = 6, 它作为一份临界资源被两个线程同时消费.

public class Test {

    private volatile int account = 6;

    public void consume(int amount) {
        // 1. 读取账户
        int currentAccount = account;

        // 2. 消费账户
        if (currentAccount >= amount) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            currentAccount -= amount;
            System.out.println(String.format("消费了%s元", amount));

            // 3. 更新账户
            account = currentAccount;
        }
    }

    public int getAccount() {
        return account;
    }

    public static void main(String[] args) {
        final Test testObj = new Test();

        Thread threadA = new Thread(() -> testObj.consume(5));
        Thread threadB = new Thread(() -> testObj.consume(6));

        threadA.start();
        threadB.start();

        try {
            threadA.join();
            threadB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(String.format("账户剩余: %s元", testObj.getAccount()));
    }

}

运行程序后, 你会发现两个线程都消费成功了, 总共消费了 11 元. 究其原因, 是因为一个线程将临界资源置于中间状态后, 另一个线程访问了这个中间状态并基于此中间状态做了进一步的处理. 结合上面的例子, 当线程 A 以的目的account 时, 就已经将 account 置于中间状态了. 直至线程 A 完成对 account 的所有操作前, account 都处于中间状态, 而这个状态对其他线程应该是不可见的. 而上例中的线程 B 因为读取了 account 的中间状态, 并基于这个中间状态做了一系列的处理, 从而导致了数据最终不一致的问题.

总结:

  • 纯粹的读操作并不会将临界资源置于中间状态;
  • 以写为目的的读操作会将临界资源置于中间状态;
  • 处于中间状态的临界资源不支持其他线程以写为目的的读操作, 更不支持操作;
  • 根据实际情况 (可否忍受脏读), 处于中间状态的临界资源可以支持其他线程纯粹的读操作;

正确理解临界资源的中间状态, 对于维护数据的一致性问题非常重要.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容