Rah*_*hra 9 java multithreading synchronization volatile thread-safety
我试图理解多线程如何在Java中工作.我明白之间的差别Volatile
和Synchronization
.
Volatile
是关于可见性,并不保证同步.当我们使用多线程环境时,每个线程在它们正在处理的变量的本地缓存上创建自己的副本.更新此值时,更新首先在本地缓存副本中发生,而不是在实变量中发生.因此,其他线程与其他线程正在更改的值无关.这就是volatile
图片.易失性字段立即写入主存储器,并从主存储器读取.
片段Thinking In Java
-
同步还会导致刷新到主内存,因此如果某个字段完全被同步的方法或块保护,则不必使其变为易失性.
如果类只有一个可变字段,那么通常只使用volatile而不是synchronized来安全.同样,您的第一选择应该是使用synchronized关键字 - 这是最安全的方法,并且尝试做任何其他事情都是有风险的.
但我的问题是,如果在同步块中,正在修改非易失性共享变量,其他线程是否会看到更新的数据?(由于有问题的变量是非易失性的,其他线程应该从缓存而不是主要主内存中读取陈旧数据)
如果对上述问题的答案是NO
,那么我可以得出结论,每次我使用同步时,我应该确保必须标记共享变量volatile
吗?
如果答案是YES
,那么这是否意味着我总是可以使用synchronization
而不是标记共享变量volatile
?
ps:在提出这个问题之前,我已经在StackOverflow和其他网站上阅读了很多答案,但我找不到我的问题的答案.
ass*_*ias 12
简化一点:
volatile
仅提供可见性:当您读取volatile
变量时,您会得到两个保证:(1)您看到对变量的最新写入,即使它是在另一个线程中执行的,(2)在写入之前的所有写入volatile
也是可见的.synchronized
为您提供可见性和原子性 - 线程观察使用同一监视器synchronized
从synchronized
块中执行的块中执行的操作将查看所有这些操作或不查看它们.所以要回答你的问题,不,如果一个变量被写入一个synchronized
块中,你就不需要标记它volatile
,前提是你总是synchronized
使用同一个监视器从一个块中读取该变量.
以下是volatile的一些示例:
static class TestVolatile {
private int i = 0;
private volatile int v = 0;
void write() {
i = 5;
v = 7;
}
void read() {
//assuming write was called beforehand
print(i); //could be 0 or 5
print(v); //must be 7
print(i); //must be 5
}
void increment() {
i = i + 1; //if two threads call the method concurrently
//i could be incremented by 1 only, not 2: no atomicity
}
}
Run Code Online (Sandbox Code Playgroud)
以下是一些例子synchronized
:
static class TestSynchronized {
private int i = 0;
private int j = 0;
void write() {
synchronized(this) {
i = 5;
j = 7;
}
}
void read_OK() {
synchronized(this) {
//assuming write was called beforehand
print(i); //must be 5
print(j); //must be 7
print(i); //must be 5
}
}
void read_NOT_OK() {
synchronized(new Object()) { //not the same monitor
//assuming write was called beforehand
print(i); //can be 0 or 5
print(j); //can be 0 or 7
}
}
void increment() {
synchronized(this) {
i = i + 1; //atomicity guarantees that if two threads call the method
//concurrently, i will be incremented twice
}
}
}
Run Code Online (Sandbox Code Playgroud)
JLS在程序中的指令上定义了一个名为"before-before"的关系.一个简短的版本可以在文档中java.util.concurrent
看到.
如果写入"在读取之前发生",则对同一变量的读操作可以看到对变量的写操作.
现在,如果两个线程仅在同步块内访问该变量,则从同步块退出可以保证其中发生的事情"发生在"之前发生的任何事情发生在同一个同步监视器的下一次锁定之后.
因此,如果线程A写入x
同步块内的变量,并且线程B从x
同一监视器上的同步块内部读取,则x
不需要是volatile - 写入"发生在"读取之前,其结果将是可见的线程B.
但是如果线程B在没有同步的情况下读取变量,那么即使线程A在同步中执行它,也不能保证写入"在之前发生",并且变量是不安全的 - 除非它是volatile
.
因此,如果您确保所有访问 - 包括读取和写入 - 都在同一监视器上的同步块内,那么您可以依赖"之前发生"关系来使您的写入可见.