我试图搜索但找不到我正在寻找的确切答案,因此提出了一个新问题。
如果您希望在多个线程之间共享任何可变对象,是否有任何最佳实践/原则/指南可以执行此操作?
或者它会根据具体情况而变化吗?
答案 0 :(得分:5)
在线程之间共享可变对象是有风险的。
最安全的方法是使对象不可变,然后你可以自由地共享它们。
如果它们必须是可变的,那么每个对象都需要使用通常的方法来确保它们自己的线程安全。 (synchronized
,AtomixX
等)。
保护单个对象的方法会有很大差异,但取决于您使用它们的方式以及使用它们的方式。
答案 1 :(得分:4)
在java中,您应该同步任何更改/读取共享对象状态的方法,这是最简单的方法。
其他策略是:
他们的关键是同步您的更新/读取以保证一致的状态,您的方式可能会有很大差异。
答案 2 :(得分:3)
线程之间共享对象的问题是由两者造成的 线程同时改变相同的数据结构。这不一定是个问题,你只需要计划所有结果。
这些是我使用的策略。
这消除了完全更改数据结构的问题。然而,有许多有用的模式无法用这种方法编写。除非你使用促进不变性的语言/ api,否则它可能效率低下。将条目添加到Scala列表要比制作Java列表的副本并向副本添加条目快得多。
这确保了一次只允许一个线程更改对象。选择要同步的对象非常重要。更改结构的一部分可能会将孔结构置于非法状态,直到进行另一次更改。同步还可以消除多线程的许多好处。
演员模型在演员中组织世界,向对方发送不可变消息。每个actor只有一个线程。演员可以包含可变性。 像Akka这样的平台为这种方法提供了基础。
这些宝石有像incrementAndGet这样的方法。它们可以使用 在没有开销的情况下实现同步的许多效果。
Java api包含为此目的而创建的并发数据结构。
在编写缓存时,通常最好冒两次工作而不是使用同步。假设你有来自dsl的已编译表达式的缓存。如果表达式被编译两次就好了,只要它最终在缓存中结束。通过允许在初始化期间执行一些额外的工作,您可能不需要在缓存访问期间使用synchronize关键字。
答案 3 :(得分:1)
有例子。 StringBuilder不是线程安全的,因此没有synchronized (builder)
块 - 结果将被破坏。试试看。
某些对象是线程安全的(例如StringBuffer),因此不需要使用synchronized块。
public static void main(String[] args) throws InterruptedException {
StringBuilder builder = new StringBuilder("");
Thread one = new Thread() {
public void run() {
for (int i = 0; i < 1000; i++) {
//synchronized (builder) {
builder.append("thread one\n");
//}
}
}
};
Thread two = new Thread() {
public void run() {
for (int i = 0; i < 1000; i++) {
//synchronized (builder) {
builder.append("thread two\n");
//}
}
}
};
one.start();
two.start();
one.join();
two.join();
System.out.println(builder);
}
答案 4 :(得分:1)
虽然已经发布了一些好的答案,但这是我在阅读Java Concurrency in Practice第3章 - 分享对象时找到的内容。
从书中引用。
对象的发布要求取决于其可变性:
- 可变对象可以通过任何机制发布;
- 必须安全发布有效的不可变对象(发布后状态不会被修改);
- 必须安全发布可变对象,并且必须是线程安全的或通过锁保护。
本书介绍了安全发布可变对象的方法:
要安全地发布对象,必须同时使对象的引用和对象的状态对其他线程可见。正确构造的对象可以通过以下方式安全地发布:
- 从静态初始值设定项初始化对象引用;
- 将对它的引用存储到易失性字段或AtomicReference中;
- 将对它的引用存储到正确构造的对象的最终字段中;或
- 将对它的引用存储到由锁定正确保护的字段中。
最后一点是指使用各种机制,例如使用并发数据结构和/或使用synchronize关键字。