《Effective Java》第 66 条:为什么要同步读写方法?

Omk*_*kar 2 java multithreading effective-java

在《Effective Java -> Item 66》中,Joshua 强调需要同步读取和写入操作以避免活性失败。

在这个特定的示例中,我认为写入方法的同步是多余的。即使删除同步写入方法后,程序运行和终止也不会出现任何问题。需要同步来查看对象的一致状态,这是通过同步读取方法来实现的。

请让我知道您对此的看法。

import java.util.concurrent.TimeUnit;

public class StopThread {

private static boolean stopRequested;

public static void main(String[] args) throws InterruptedException {
    new Thread(new Runnable() {

        @Override
        public void run() {
            int i = 0;
            while (!isStopRequested())
                i++;
        }
    }).start();
    ;
    TimeUnit.SECONDS.sleep(1);
    setStopRequested(true);
}

private static synchronized boolean isStopRequested() {
    return stopRequested;
}

private static void setStopRequested(boolean stopRequested) {
    StopThread.stopRequested = stopRequested;
}
}
Run Code Online (Sandbox Code Playgroud)

hag*_*wal 6

您提到的示例可能最适合演示在没有同步(或易失性)的情况下如何无法保证线程本地内存中的值何时会刷新到主内存,但这个示例当然不是最适合演示“读写并发问题”。

我想你可能误解了例子的目的,目的是为了展示在没有同步的情况下线程通信的效果。请阅读以下同一项目#66 的摘录:

即使没有同步,StopThread 中同步方法的操作也是原子的。换句话说,这些方法上的同步仅用于其通信效果,而不是用于互斥。

您认为它起作用的原因是因为在没有同步的情况下,JVM 无法“保证”线程本地内存中的值何时会刷新到主内存,这意味着它可能根本不会刷新,也可能会刷新但无法保证“何时”。当你运行它时,值会被刷新,但它不一定总是会被刷新,所以这就是“保证”出现的地方,如果你使用同步(或易失性,取决于场景),那么 JVM 保证“发生” -before关系,这只是保证将值从线程本地内存刷新到主内存将“发生在”任何线程可以从主内存读取值之前。

在没有同步的情况下检查读写相关并发问题的效果的一个更好的例子可能是流行的银行帐户借记贷记示例,下面是快速示例:

public class AccountDebitCredit {

    private int accountBalance = 100;

    public static void main(String[] args) throws InterruptedException {
        final AccountDebitCredit accountDebitCredit = new AccountDebitCredit();

        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    // if you remove synchronization from t1 and t2, then there would be concurrency issues.
                    synchronized (accountDebitCredit) {
                        accountDebitCredit.accountBalance = accountDebitCredit.accountBalance + 100;
                    }
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    // if you remove synchronization from t1 and t2, then there would be concurrency issues.
                    synchronized (accountDebitCredit) {
                        accountDebitCredit.accountBalance = accountDebitCredit.accountBalance - 100;
                    }
                }
            }
        });

        System.out.println(accountDebitCredit.accountBalance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(accountDebitCredit.accountBalance);
    }

}
Run Code Online (Sandbox Code Playgroud)