【发布时间】:2014-05-16 02:34:28
【问题描述】:
我正在阅读 Effective Java 书和关于最小化可变性的章节第 15 条。 也许我无法理解线程安全的概念,因为我在并发方面没有太多经验。我能否举个例子来说明不可变对象如何始终是线程安全的?
提前谢谢你!
【问题讨论】:
标签: java thread-safety java.util.concurrent
我正在阅读 Effective Java 书和关于最小化可变性的章节第 15 条。 也许我无法理解线程安全的概念,因为我在并发方面没有太多经验。我能否举个例子来说明不可变对象如何始终是线程安全的?
提前谢谢你!
【问题讨论】:
标签: java thread-safety java.util.concurrent
不可变对象是线程安全的,因为它们不能被修改。
如果一百万个线程同时访问同一个对象并不重要,因为没有一个线程可以改变对象。
【讨论】:
线程安全意味着更改所述对象不会对使用该对象的其他线程产生不利影响。不可变对象无法更改。因此,根据设计,不可变对象是线程安全的,因为它们一开始就不会发生任何变化。
请记住,线程可能会共享引用。如果更改引用指向的对象(不更改对象本身,而是将引用与 = 符号一起重新分配给另一个对象),则会危及线程安全。
【讨论】:
假设你有一个柜台:
class Counter {
private int counter = 0;
public void increment() {
counter++;
}
public int getCounter() {
return counter;
}
}
说这是你的主要方法:
public static void main(String[] args) {
final Counter counter = new Counter();
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch endLatch = new CountDownLatch(4);
final Runnable r = () -> {
try {
startLatch.await();
} catch (final InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
counter.increment();
if (counter.getCounter() % 10 == 0) {
System.out.println(counter.getCounter());
}
}
endLatch.countDown();
};
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
startLatch.countDown();
try {
endLatch.await();
} catch (final InterruptedException e) {
e.printStackTrace();
}
}
它很长,但基本上它所做的只是创建一个 Counter,然后创建 4 个线程,每个线程将计数器递增一百次,如果该值是 20 的倍数,则打印计数器的值。什么输出你明白了吗?
20
40
60
80
100
141 // <-- Huh? Not a multiple of 20?
120 // <-- What's up with the order here?
180
220
240
160 // <-- This is way out of place...
280
300
260
200
320
340
360
380
// <-- missing 400?
嗯,这是一个惊喜。错误的值,不合适的值等等......
问题是共享像Counter 这样具有可变状态的对象会带来很多困难。您必须处理锁、同步等,才能使可变对象正常运行。在这种情况下,同步相对容易,但要使复杂的对象正确同步却很难。如果需要示例,请查看 java.util.concurrent 中的类。
不可变对象的好处在于它们避免了这个问题,因为它们不能被修改。所以不管有多少线程对一个不可变对象做某事,你可以绝对确定它不会改变,所以你不会处理这样奇怪的结果。不可变的Counter 将相当无用,但像String 这样的不可变且可以跨线程共享而无需担心跨线程同步更改的东西在并发世界中非常有用。
【讨论】:
您可以通过删除所有设置器和任何更改对象状态的方法来使对象不可变。
字符串是不可变对象的例子。 无论有多少线程访问一个字符串,它们都无法更改它。每当您修改一个字符串时,都会创建一个新对象。
所以多个线程可以读取状态但永远不能更新状态。
【讨论】:
private,并且它的任何方法都没有更新它的任何字段,那么它实际上是不可变的。这与字段都是final. 的真正 不可变对象略有不同,不同之处在于,有效 不可变对象只有在安全发布时才是线程安全的。谷歌搜索“Java”和“安全发布”以了解更多信息。