我知道有很多问题,但我仍然不太明白。我知道这两个关键字的作用,但我无法确定在某些情况下使用哪个。以下是一些我正在尝试确定最佳使用的示例。
示例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;
}
}
非常感谢任何帮助。
答案 0 :(得分:38)
一个简单的答案如下:
synchronized
始终可用于为您提供线程安全/正确的解决方案,
volatile
可能会更快,但只能用于在有限的情况下为您提供线程安全/正确。
如有疑问,请使用synchronized
。正确性比表现更重要。
表征可以安全使用volatile
的情况涉及确定每个更新操作是否可以作为单个volatile变量的单个原子更新来执行。如果操作涉及访问其他(非最终)状态或更新多个共享变量,则只能使用volatile进行安全操作。你还需要记住:
long
或double
的更新可能不是原子的,++
和+=
等Java运算符不是原子的。术语:如果操作完全发生,或者根本不发生,则操作是“原子的”。 “不可分割”一词是同义词。
当我们谈论原子性时,我们通常从外部观察者的角度来看意味着原子性;例如与执行操作的线程不同的线程。例如,++
从另一个线程的角度来看不是原子的,因为该线程可能能够观察到在操作中间递增的字段的状态。实际上,如果该字段是long
或double
,甚至可能会观察到既不是初始状态也不是最终状态的状态!
答案 1 :(得分:18)
synchronized
关键字
synchronized
表示变量将在多个线程之间共享。它用于通过“锁定”对变量的访问来确保一致性,这样一个线程就无法修改它而另一个线程使用它。
经典示例:更新指示当前时间的全局变量
incrementSeconds()
函数必须能够不间断地完成,因为它在运行时会在全局变量time
的值中创建临时不一致。如果没有同步,另一个函数可能会看到time
“12:60:00”,或者在标有>>>
的注释中,当时间真的是“时,它会看到”11:00:00“ 12:00: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
关键字
volatile
只是告诉编译器不要对变量的常量做出假设,因为它可能会在编译器通常不期望它时发生变化。例如,数字恒温器中的软件可能具有指示温度的变量,其值由硬件直接更新。它可能会在正常变量不会发生变化的地方发生变化。
如果degreesCelsius
未声明为volatile
,编译器可以自由优化:{/ p>
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);
}
}
通过声明degreesCelsius
为volatile
,您告诉编译器每次运行循环时都必须检查其值。
<强>摘要强>
简而言之, synchronized
可让您控制对变量的访问,因此您可以保证更新是原子的(即,一组更改将作为一个单元应用;否其他线程可以在半更新时访问该变量。您可以使用它,以确保数据的一致性。另一方面, volatile
是承认变量的内容超出了您的控制范围,因此代码必须假设它可以随时更改。
答案 2 :(得分:9)
您的帖子中没有足够的信息来确定发生了什么,这就是为什么您获得的所有建议都是关于volatile
和synchronized
的一般信息。
所以,这是我的一般建议:
在编写 - 编译 - 运行程序的循环中,有两个优化点:
所有这些意味着指令很可能不会按照您编写它们的顺序执行,无论是否必须维护此顺序以确保多线程环境中的程序正确性。您将在文献中经常发现的一个典型例子是:
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架构,上述代码可能永远不会在多处理器系统上终止。这是因为stop
的值将缓存在CPU运行线程t
的寄存器中,这样线程永远不会再次从主内存中读取值,即使主线程已更新它与此同时。
为了对抗这种情况,我们引入了记忆围栏。这些是特殊说明,不允许在围栏之后使用围栏后的说明重新排序围栏之前的常规指令。一种这样的机制是volatile
关键字。标记为volatile
的变量未由编译器/ CPU优化,并且将始终直接写入/读取主存储器。简而言之,volatile
可确保跨CPU核心的变量值的可见性。
可见性很重要,但不应与原子性混淆。即使变量声明为volatile
,递增相同共享变量的两个线程也可能产生不一致的结果。这是因为在某些系统上,增量实际上被转换为可在任何点上中断的汇编指令序列。对于此类情况,需要使用synchronized
关键字等关键部分。这意味着只有一个线程可以访问synchronized
块中包含的代码。关键部分的其他常见用途是对共享集合的原子更新,当通常迭代集合而另一个线程正在添加/删除项目时将导致抛出异常。
最后有两点有趣:
synchronized
以及其他一些构造(如Thread.join
)将隐式引入内存栅栏。因此,在synchronized
块内增加变量不要求变量也是volatile
,假设它是唯一被读/写的地方。AtomicInteger
,AtomicLong
等中的方法。这些方法比{{1因为如果另一个线程已经锁定了锁,它们不会触发上下文切换。它们在使用时也会引入内存栅栏。答案 3 :(得分:1)
注意:在您的第一个示例中,字段serverSocket
实际上从未在您显示的代码中初始化。
关于同步,它取决于ServerSocket
类是否是线程安全的。 (我假设它是,但我从未使用它。)如果是,你不需要在它周围同步。
在第二个示例中,int
个变量可以原子更新,因此volatile
就足够了。
答案 4 :(得分:1)
volatile
解决了CPU核心的“可见性”问题。因此,本地寄存器的值被刷新并与RAM同步。但是,如果我们需要一致的值和原子操作,我们需要一种机制来保护关键数据。这可以通过synchronized
阻止或显式锁定来实现。