在阅读" 实践中的Java并发 "和" 实践中的OSGI "后,我发现了一个非常有趣的特定主题; 安全出版.以下内容来自JCIP:
要安全地发布对象,必须同时使对象的引用和对象的状态对其他线程可见.正确构造的对象可以通过以下方式安全发布:
- 从静态初始化程序初始化对象引用.
- 将对它的引用存储到volatile字段中.
- 将对它的引用存储到最终字段中.
- 将对它的引用存储到由(同步)锁定正确保护的字段中.
我的第一个问题:有多少java开发人员知道这个(问题)?有多少真实世界的Java应用程序真正遵循这个,这真的是一个真正的问题吗?我有一种感觉,99%的已实现的JVM不是那种"邪恶",即一个线程不能保证(事实上它实际上(几乎)"不可能")看到陈旧数据只是因为引用不遵循上面的"安全出版成语".
我正在读Brian Goetz的一本书"实践中的Java并发".第3.5和3.5.1段包含我无法理解的陈述.
请考虑以下代码:
public class Holder {
private int value;
public Holder(int value) {
this.value = value;
}
public void assertValue() {
if (value != value) throw new AssertionError("Magic");
}
}
class HolderContainer {
// Unsafe publication
public Holder holder;
public void init() {
holder = new Holder(42);
}
}
Run Code Online (Sandbox Code Playgroud)
作者说:
因此,根据文本,有些不幸的时机可能值= 0; 在下一刻,值= 42.
我同意第1点,Object构造函数首先使用默认值填充字段.但我不明白第2和第3点.
让我们更新作者代码并考虑以下示例:
public class Holder {
int value;
public Holder(int value) {
//Sleep to prevent constructor to finish too early
try { …
Run Code Online (Sandbox Code Playgroud) 我知道你可以安全地发布一个非线程安全的对象,方法是将一个final
或一个volatile
字段的引用写入,稍后将被其他一个线程读取,前提是在发布时,创建该对象的线程会丢弃对它的引用,以便它不能再干扰或不安全地观察对象在另一个线程中的使用.
但在这个例子中,没有显式final
字段,只有final
局部变量. 如果调用者丢弃其引用unsafe
,这是安全的出版物吗?
void publish(final Unsafe unsafe) {
mExecutor.execute(new Runnable() {
public void run() {
// do something with unsafe
}
}
}
Run Code Online (Sandbox Code Playgroud)
我发现了一些Q&As,就像这个一样,暗示final
局部变量被隐式"复制"到匿名类中.这是否意味着上面的例子等同于此?
void publish(final Unsafe unsafe) {
mExecutor.execute(new Runnable() {
final Unsafe mUnsafe = unsafe;
public void run() {
// do something with mUnsafe
}
}
}
Run Code Online (Sandbox Code Playgroud)
编辑以澄清:
Unsafe
可能是什么,但说它是这样的:
public class Unsafe {
public int x;
}
Run Code Online (Sandbox Code Playgroud)
并且mExecutor
是满足合同的任何东西Executor
.
代码示例:
class Obj1 {
int f1 = 0;
}
volatile Obj1 v1;
Obj1 v2;
Thread 1 | Thread 2 | Thread 3
-------------------------------------------------
var o = new Obj1(); | |
o.f1 = 1; | |
v1 = o; | |
| v2 = v1; |
| | var r1 = v2.f1;
Is (r1 == 0) possible?
Run Code Online (Sandbox Code Playgroud)
这里的对象o
:
Thread 1
到Thread 2
通过volatile
字段v1
Thread 2
到Thread 3 …
通过阅读Java Concurrency in Practice
我可以看到:
要安全地发布对象,必须同时使对象的引用和对象的状态对其他线程可见.正确构造的对象可以通过以下方式安全发布:
但是,我对第二个成语感到困惑.因为volatile
只能保证引用对另一个线程可见,但它没有引用的对象构造的同步.那么如何保证可变对象被正确构造,构造该对象的线程被另一个线程中断了?
由于StringBuffer
是线程安全的,因此可以安全地发布.考虑StringBuffer
(来源)的公共构造函数:
public StringBuffer() {
super(16);
}
Run Code Online (Sandbox Code Playgroud)
在哪里super(16)
指定这个:
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
Run Code Online (Sandbox Code Playgroud)
其中值声明为
char[] value;
Run Code Online (Sandbox Code Playgroud)
问题:如何StringBuffer
安全发布?
我有以下课程:
public class Holder{
public final StringBuffer sb = new StringBuffer();
}
Run Code Online (Sandbox Code Playgroud)
它可以被视为安全出版物吗?我想,它不可能.
final
保证我们会看到参考的新值sb
.但写入sb
内部状态AbstractStringBuilder(int capacity)
并不是同步的.因此,有没有happens-before
这反过来又意味着,从读命令value
调用发生时,sb.append(2);
并写入value
在构造函数是活泼的.
你能帮忙理解这个吗?也许我错过了什么......
考虑以下 Kotlin 代码:
import kotlin.concurrent.thread
fun main() {
println("Press <Enter> to terminate.")
var interrupted = false
val worker = thread {
while (!interrupted) {
println("Working...")
Thread.sleep(1000L)
}
}
System.`in`.read()
println("Terminating...")
interrupted = true
worker.join()
println("Terminated.")
}
Run Code Online (Sandbox Code Playgroud)
以及使用协程重写的相同示例:
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
println("Press <Enter> to terminate.")
var interrupted = false
val worker = launch(Dispatchers.IO) {
while (!interrupted) {
println("Working...")
delay(1000L)
}
}
System.`in`.read()
println("Terminating...")
interrupted = true
worker.join()
println("Terminated.")
}
Run Code Online (Sandbox Code Playgroud)
这两个示例在大多数情况下都可以工作,但都被破坏了,因为在字节码级别,boolean …
concurrency multithreading kotlin safe-publication kotlin-coroutines
来自Java Concurrency In Practice一书:
要安全地发布对象,必须同时使对对象的引用和对象的状态对其他线程可见。正确构造的对象可以通过以下方式安全地发布:
- 从静态初始化器初始化对象引用;
- 将对其的引用存储到 volatile 字段或 AtomicReference 中;
- 将对其的引用存储到正确构造的对象的最终字段中;或者
- 将对其的引用存储到由锁正确保护的字段中。
我的问题是:
为什么要点 3 有约束:“正确构造的对象”,而要点 2 没有?
以下代码是否安全地发布map
实例?我认为代码符合要点 2 的条件。
public class SafePublish {
volatile DummyMap map = new DummyMap();
SafePublish() throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
// Safe to use 'map'?
System.out.println(SafePublish.this.map);
}
}).start();
Thread.sleep(5000);
}
public static void main(String[] args) throws InterruptedException {
SafePublish safePublishInstance = new SafePublish();
}
public …
Run Code Online (Sandbox Code Playgroud) java concurrency volatile java-memory-model safe-publication