何时使用易失性和同步

2022-09-01 09:18:10

我知道有很多关于这个问题的问题,但我仍然不太明白。我知道这两个关键字的作用,但我无法确定在某些情况下使用哪个关键字。以下是我试图确定哪个是最好使用的几个示例。

示例 1:

import java.net.ServerSocket;

public class Something extends Thread {

    private ServerSocket serverSocket;

    public void run() {
        while (true) {
            if (serverSocket.isClosed()) {
                ...
            } else { //Should this block use synchronized (serverSocket)?
                //Do stuff with serverSocket
            }
        }
    }

    public ServerSocket getServerSocket() {
        return serverSocket;
    }

}

public class SomethingElse {

    Something something = new Something();

    public void doSomething() {
        something.getServerSocket().close();
    }

}

示例 2:

public class Server {

    private int port;//Should it be volatile or the threads accessing it use synchronized (server)?

    //getPort() and setPort(int) are accessed from multiple threads
    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

}

任何帮助都非常感谢。


答案 1

一个简单的答案如下:

  • synchronized总是可以用来给你一个线程安全/正确的解决方案,

  • volatile可能会更快,但只能在有限的情况下用于为您提供线程安全/正确的线程。

如有疑问,请使用 。正确性比性能更重要。synchronized

描述可以安全使用的情况涉及确定每个更新操作是否可以作为单个易失性变量的单个原子更新来执行。如果操作涉及访问其他(非最终)状态或更新多个共享变量,则仅使用易失性无法安全地完成。您还需要记住:volatile

  • 对非易失性或 a 的更新可能不是原子的,以及longdouble
  • Java运算符喜欢并且不是原子的。+++=

术语:如果操作完全发生,或者根本不发生,则操作是“原子”操作。术语“不可分割”是一个同义词。

当我们谈论原子性时,我们通常是指从外部观察者的角度来看的原子性;例如,与执行操作的线程不同的线程。例如,从另一个线程的角度来看,++ 不是原子的,因为该线程可能能够观察到在操作过程中字段递增的状态。事实上,如果场是场或双子,甚至有可能观察到一个既不是初始状态也不是最终状态的状态!


答案 2

同步关键字

synchronized表示变量将在多个线程之间共享。它用于通过“锁定”对变量的访问来确保一致性,以便一个线程在另一个线程使用它时无法修改它。

经典示例:更新指示当前时间
的全局变量 该函数必须能够不间断地完成,因为在运行时,它会在全局变量的值中产生暂时的不一致。如果没有同步,另一个函数可能会看到“12:60:00”,或者在标记为 的注释处,当时间实际上是“12:00:00”时,它将看到“11:00:00”,因为小时数尚未增加。incrementSeconds()timetime>>>

void incrementSeconds() {
  if (++time.seconds > 59) {      // time might be 1:00:60
    time.seconds = 0;             // time is invalid here: minutes are wrong
    if (++time.minutes > 59) {    // time might be 1:60:00
      time.minutes = 0;           // >>> time is invalid here: hours are wrong
      if (++time.hours > 23) {    // time might be 24:00:00
        time.hours = 0;
      }
    }
  }

易失性关键字

volatile简单地告诉编译器不要对变量的常量性做出假设,因为它可能会在编译器通常不会期望它的时候改变。例如,数字恒温器中的软件可能具有指示温度的变量,其值由硬件直接更新。它可能会在正常变量不会改变的地方发生变化。

如果未声明为 ,编译器可以自由地对此进行优化:degreesCelsiusvolatile

void controlHeater() {
  while ((degreesCelsius * 9.0/5.0 + 32) < COMFY_TEMP_IN_FAHRENHEIT) {
    setHeater(ON);
    sleep(10);
  }
}

进入这个:

void controlHeater() {
  float tempInFahrenheit = degreesCelsius * 9.0/5.0 + 32;

  while (tempInFahrenheit < COMFY_TEMP_IN_FAHRENHEIT) {
    setHeater(ON);
    sleep(10);
  }
}

通过声明为 be,您可以告诉编译器,每次通过循环运行时,它都必须检查其值。degreesCelsiusvolatile

总结

简而言之,synchronized允许您控制对变量的访问,因此您可以保证更新是原子的(即,一组更改将作为一个单元应用;当变量半更新时,没有其他线程可以访问该变量)。您可以使用它来确保数据的一致性。另一方面,volatile是承认变量的内容超出了您的控制范围,因此代码必须假设它可以随时更改。