我不明白为什么java中的不可变对象本身就是线程安全的

时间:2014-05-16 02:34:29

标签: java thread-safety java.util.concurrent

我正在阅读有效的Java书和关于最小化可变性的章节。 也许我很难理解线程安全的概念,因为我在并发方面经验不多。我可以得到一个示例,说明不可变对象如何始终是线程安全的吗?

提前谢谢!

4 个答案:

答案 0 :(得分:3)

不可变对象是线程安全的,因为它们无法修改。

如果一百万个线程同时访问同一个对象并不重要,因为没有一个线程可以改变该对象。

答案 1 :(得分:2)

线程安全意味着更改所述对象不会对正在使用该对象的其他线程产生负面影响。不可变对象无法更改。因此,通过设计,不可变对象是线程安全的,因为它们开始时不会发生任何变化。

请记住,线程可能会共享引用。如果更改引用所指向的对象(不更改对象本身,但将引用重新分配给另一个对象以及=符号),则会危及线程安全。

答案 2 :(得分:0)

假设你有一个柜台:

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这样的东西是不可变的并且可以跨线程共享而不用担心跨线程同步变化在并发世界中非常有用。

答案 3 :(得分:0)

您可以通过删除所有setter和任何更改对象状态的方法来使对象不可变。

String是不可变对象的示例。 无论有多少线程访问String,他们都无法更改它。无论何时修改String,都会创建一个新对象。

因此多线程可以读取状态,但永远不能更新状态。