代码之家  ›  专栏  ›  技术社区  ›  rico

当volatile关键字真的是必要的?

  •  0
  • rico  · 技术社区  · 2 年前

    这个代码取自书本 Java线程和并发实用程序 Jeff Friesen:

    public class PC {
        public static void main(String[] args) {
            Shared s = new Shared();
            new Producer(s).start();
            new Consumer(s).start();
        }
    }
    
    class Shared {
        private char c;
        private volatile boolean writeable = true;
    
        synchronized void setSharedChar(char c) {
            while (!writeable)
                try {
                    wait();
                } catch (InterruptedException ie)  {
                }
            this.c = c;
            writeable = false;
            notify();
        }
    
        synchronized char getSharedChar() {
            while (writeable)
                try {
                    wait();
                } catch (InterruptedException ie)  {
                }
            writeable = true;
            notify();
            return c;
        }
    }
    
    class Producer extends Thread {
        private final Shared s;
    
        Producer(Shared s) {
            this.s = s;
        }
    
        @Override
        public void run() {
            for (char ch = 'A'; ch <= 'Z'; ch++) {
                synchronized(s) {
                    s.setSharedChar(ch);
                    System.out.println(ch + " produced by producer.");
                }
            }
        }
    }
    
    class Consumer extends Thread {
        private final Shared s;
    
        Consumer(Shared s) {
            this.s = s;
        }
    
        @Override
        public void run() {
            char ch;
            do {
                synchronized(s) {
                    ch = s.getSharedChar();
                    System.out.println(ch + " consumed by consumer.");
                }
            } while (ch != 'Z');
        }
    }
    

    我不明白为什么有必要 volatile 这个 writeable 字段,因为它只能由访问 synchronized 方法,其效果与volatile相同(可见性+写入互斥)。以及为什么字段 可写的 需要 不稳定的 而不是字段 c 在这种情况下。

    下一个代码也有同样的问题 balance 字段:为什么它需要是可变的,因为它已经被访问了 同步的 withdraw 方法

    public class CheckingAccount {
        private volatile int balance;
    
        public CheckingAccount(int initialBalance) {
            balance = initialBalance;
        }
    
        public synchronized boolean withdraw(int amount) {
            if (amount <= balance) {
                try {
                    Thread.sleep((int) (Math.random() * 200));
                } catch (InterruptedException ie) {
                }
                balance -= amount;
                return true;
            }
            return false;
        }
    
        public static void main(String[] args) {
            final CheckingAccount ca = new CheckingAccount(100);
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    String name = Thread.currentThread().getName();
                    for (int i = 0; i < 10; i++)
                        System.out.println (name + " withdraws $10: " +
                    ca.withdraw(10));
                }
            };
            Thread thdHusband = new Thread(r);
            thdHusband.setName("Husband");
            Thread thdWife = new Thread(r);
            thdWife.setName("Wife");
            thdHusband.start();
            thdWife.start();
        }
    }
    

    有人能解释一下吗?

    1 回复  |  直到 2 年前
        1
  •  5
  •   rzwitserloot    2 年前

    你正确地理解了这些想法。 volatile 在这里是完全无用的,不应该使用。如果这本书没有介绍这些片段作为什么的例子 要做到这一点,您还粘贴了整个片段(即,没有您删除的访问这些字段的方法 不是 synchronized 在…上 this )-那么这是书中一个非常痛苦的错误。如果这是由于作者不了解JMM本身而导致的错误,那就意味着这本书应该被扔进垃圾桶。如果这只是示例中的一个错误,也许是因为它最初是作为一个使用 不稳定的 后来被换成了使用 同步的 ,但作者忘记删除 不稳定的 ,这仍然是一个非常严重的编辑错误。