有效的Java项目66:为什么同步读写方法?

有效的Java项目66:为什么同步读写方法?,java,multithreading,effective-java,Java,Multithreading,Effective Java,在有效的Java->Item66中,Joshua强调需要同步读写操作以避免活动性失败 在这个特定的例子中,我认为写方法上的同步是多余的。即使在删除“写时同步”方法后,程序运行和终止都不会出现任何问题。 同步需要查看对象的一致状态,这是通过读取时同步方法实现的 请让我知道你对此的看法 import java.util.concurrent.TimeUnit; public class StopThread { private static boolean stopRequested; pub

在有效的Java->Item66中,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;
}
}

我稍微修改了你的代码,但现在根本没有完成。当你不使用原子变量时,你永远不会知道接下来会发生什么。JIT可以优化读/写操作

import java.util.concurrent.TimeUnit;

public class StopThread {

    private static boolean stopRequested = false;

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

            @Override
            public void run() {
                int i = 0;
                while (!stopRequested)
                    i++;
            }
        }).start();
        TimeUnit.SECONDS.sleep(10);
        stopRequested = true;
        System.out.println("Set to true");
        Thread.sleep(40 * 1000L);
    }
}

在我的机器上,这个代码永远不会结束。由于修改后的代码所做的工作几乎相同,很容易看出您依赖的是JIT行为,这种行为在将来可能会发生变化。也许在下一个java版本中,您的代码也无法完成。

我稍微修改了您的代码,但现在它根本无法完成。当你不使用原子变量时,你永远不会知道接下来会发生什么。JIT可以优化读/写操作

import java.util.concurrent.TimeUnit;

public class StopThread {

    private static boolean stopRequested = false;

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

            @Override
            public void run() {
                int i = 0;
                while (!stopRequested)
                    i++;
            }
        }).start();
        TimeUnit.SECONDS.sleep(10);
        stopRequested = true;
        System.out.println("Set to true");
        Thread.sleep(40 * 1000L);
    }
}

在我的机器上,这个代码永远不会结束。由于修改后的代码所做的工作几乎相同,很容易看出您依赖的是JIT行为,这种行为在将来可能会发生变化。可能在下一个java版本中,您的代码也无法完成。

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

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

即使没有同步,StopThread中同步方法的操作也是原子的换言之 这些方法上的同步仅用于通信 影响,而不是相互排斥。

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

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

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);
    }

}
公共类帐户DebitCredit{
私人国际账户余额=100;
公共静态void main(字符串[]args)引发InterruptedException{
最终账户DebitCredit AccountDebitCredit=新账户DebitCredit();
线程t1=新线程(新的可运行线程(){
@凌驾
公开募捐{
对于(int i=0;i<10000;i++){
//如果从t1和t2中删除同步,则会出现并发问题。
已同步(accountDebitCredit){
accountDebitCredit.accountBalance=accountDebitCredit.accountBalance+100;
}
}
}
});
线程t2=新线程(新可运行(){
@凌驾
公开募捐{
对于(int i=0;i<10000;i++){
//如果从t1和t2中删除同步,则会出现并发问题。
已同步(accountDebitCredit){
accountDebitCredit.accountBalance=accountDebitCredit.accountBalance-100;
}
}
}
});
系统输出打印项次(accountDebitCredit.accountBalance);
t1.start();
t2.start();
t1.join();
t2.连接();
系统输出打印项次(accountDebitCredit.accountBalance);
}
}

您提到的示例最适合演示在没有同步(或易失性)的情况下,线程本地内存中的值何时会刷新到主存的问题,但这个示例肯定不适合演示“读写并发问题”

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

即使没有同步,StopThread中同步方法的操作也是原子的换言之 这些方法上的同步仅用于通信 影响,而不是相互排斥。

您认为它可以工作的原因是,在没有同步的情况下,当线程本地内存中的值将被刷新到主内存时,JVM不会做出“保证”,这意味着它可能根本不会刷新,或者它可能会刷新,但“何时”不保证。当您运行它时,值会被刷新,但并不一定总是被刷新,所以这就是“保证”的含义,如果您使用同步(或volatile,取决于场景),那么JVM保证“发生在”之前的关系,这只会保证这一点