Java 何时使用volatile和synchronized
我知道这方面有很多问题,但我还是不太明白。我知道这两个关键字的作用,但我无法确定在某些场景中使用哪个关键字。这里有几个例子,我正试图确定哪一个是最好的使用 例1: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) {
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体系结构,上述代码可能永远不会在多处理器系统上终止。这是因为