Java 何时使用volatile和synchronized

Java 何时使用volatile和synchronized,java,multithreading,Java,Multithreading,我知道这方面有很多问题,但我还是不太明白。我知道这两个关键字的作用,但我无法确定在某些场景中使用哪个关键字。这里有几个例子,我正试图确定哪一个是最好的使用 例1: import java.net.ServerSocket; public class Something extends Thread { private ServerSocket serverSocket; public void run() { while (true) {

我知道这方面有很多问题,但我还是不太明白。我知道这两个关键字的作用,但我无法确定在某些场景中使用哪个关键字。这里有几个例子,我正试图确定哪一个是最好的使用

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

}

非常感谢您的帮助。

注意:在第一个示例中,
serverSocket
字段实际上从未在您显示的代码中初始化

关于同步,这取决于
ServerSocket
类是否是线程安全的。(我想是的,但我从未使用过。)如果是的话,您不需要围绕它进行同步


在第二个示例中,
int
变量可以自动更新,因此
volatile
就足够了。

一个简单的答案如下:

  • synchronized
    始终可用于为您提供线程安全/正确的解决方案

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

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

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

  • 对非易失性
    long
    double
    的更新可能不是原子的,并且
  • ++
    +=
    这样的Java操作符不是原子的

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


当我们谈论原子性时,我们通常是指从外部观察者的角度来看原子性;e、 g.与执行操作的线程不同的线程。例如,从另一个线程的角度看, ++ >代码>不是原子的,因为该线程可以观察到在操作的中间增加了该字段的状态。实际上,如果字段是长的或双的,甚至可能观察到既不是初始状态也不是最终状态的状态

同步的
关键字

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

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

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
只是告诉编译器不要对变量的常量进行假设,因为它可能会在编译器通常不期望的情况下发生变化。例如,数字恒温器中的软件可能有一个指示温度的变量,其值由硬件直接更新。它可能会在正常变量不会改变的地方发生变化

如果
degreescelius
未声明为
volatile
,则编译器可以自由优化:

void controlHeater() {
  while ((degreesCelsius * 9.0/5.0 + 32) < COMFY_TEMP_IN_FAHRENHEIT) {
    setHeater(ON);
    sleep(10);
  }
}
void控制加热器(){
而((摄氏度*9.0/5.0+32)<舒适的华氏温度){
设置加热器(打开);
睡眠(10);
}
}
为此:

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

  while (tempInFahrenheit < COMFY_TEMP_IN_FAHRENHEIT) {
    setHeater(ON);
    sleep(10);
  }
}
void控制加热器(){
浮子温度恒河=摄氏度*9.0/5.0+32;
而(温度低于舒适的温度){
设置加热器(打开);
睡眠(10);
}
}
通过将
degreescelius
声明为
volatile
,您告诉编译器每次运行循环时都必须检查其值

摘要


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

您的帖子中没有足够的信息来确定发生了什么,这就是为什么您得到的所有建议都是关于
volatile
synchronized
的一般信息

因此,以下是我的一般建议:

在编写和运行程序的过程中,有两个优化点:

  • 在编译时,编译器可能会尝试对指令重新排序或优化数据缓存
  • 在运行时,CPU有自己的优化,比如缓存和无序执行
所有这一切都意味着指令很可能不会按照您编写它们的顺序执行,而不管为了确保多线程环境中的程序正确性,是否必须保持这种顺序。你在文献中经常会发现的一个经典例子是:

class ThreadTask implements Runnable {
    private boolean stop = false;
    private boolean work;

    public void run() {
        while(!stop) {
           work = !work; // simulate some work
        } 
    }

    public void stopWork() {
        stop = true; // signal thread to stop
    }

    public static void main(String[] args) {
        ThreadTask task = new ThreadTask();
        Thread t = new Thread(task);
        t.start();
        Thread.sleep(1000);
        task.stopWork();
        t.join();
    }
}
根据编译器优化和CPU体系结构,上述代码可能永远不会在多处理器系统上终止。这是因为