业务的可变性和不可变性分析_不可变性真的意味着线程安全吗?

业务的可变性和不可变性分析

我经常阅读有关“如果对象是不可变的,则它是线程安全的”的文章。 实际上,我从未找到过一篇使我相信不可变意味着线程安全的文章。 即使是Brian Goetz的Java Concurrency in Practice一书中关于不变性的一本书,也没有完全使我满意。 在这本书中,我们可以在一个框架中逐字阅读: 不可变对象始终是线程安全的 。 我认为这句话值得更多解释。

因此,我将尝试定义不变性及其与线程安全性的关系。

定义 不变性

我的定义是“不可变的对象是在构造后状态不会改变的对象”。 我故意含糊其词,因为没有人真正同意确切的定义。

线程安全

您可以在Internet上找到许多不同的“线程安全”定义。 定义它实际上非常棘手。 我会说线程安全代码是在多线程环境中具有预期行为的代码。 我让您定义“预期行为”…

字符串示例

让我们看一下String的代码(实际上只是一部分代码...):

public class String {
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    public String(char[] value) {
        this.value = Arrays.copyOf(value, value.length);
    }

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
}

String被认为是不可变的。 看一下它的实现,我们可以推断出一件事:不可变的对象可以更改其内部状态(在本例中为延迟加​​载的哈希码),只要它在外部不可见即可。

现在,我将以一种非线程安全的方式重写hashcode方法:

public int hashCode() {
        if (hash == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                hash = 31 * hash + val[i];
            }
        }
        return hash;
    }

如您所见,我删除了局部变量h并直接影响了变量hash 。 此实现不是线程安全的! 如果多个线程同时调用hashcode ,则每个线程的返回值可能不同。 问题是,这堂课是一成不变的吗? 由于两个不同的线程可以看到不同的哈希码,因此从外部角度来看,我们具有状态更改,因此它不是不可变的。

我们可以得出这样的结论: String是不可变的, 因为它是线程安全的,而不是相反的。 所以……说“做一些不可变的对象,它是线程安全的!”有什么意义? 但是请注意,您必须使不可变对象具有线程安全性!”

ImmutableSimpleDateFormat示例

在下面,我写了一个类似于SimpleDateFormat的类。

public class VerySimpleDateFormat {

    private final DateFormat formatter = SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT);

    public String format(Date d){
        return formatter.format(d);
    }
}

该代码不是线程安全的,因为SimpleDateFormat.format不是。

这个对象是不变的吗? 好问题! 我们已尽最大努力使所有字段均不可修改,我们不使用任何建议设置对象状态将改变的设置方法或方法。 实际上,内部SimpleDateFormat更改其状态,这就是它不安全线程的原因。 由于对象图中的某些内容发生了变化,因此即使它看起来像它,它也不是不变的。问题甚至不在于SimpleDateFormat更改其内部状态,而是在于它以一种非线程安全的方式进行操作。

总结这个例子,创建一个不可变的类并不容易。 最后一个关键字还不够,您必须确保对象的对象字段不会更改其状态,这有时是不可能的。

不可变的对象可以具有非线程安全的方法(没有魔术!)

让我们看一下下面的代码。

public class HelloAppender {

    private final String greeting;

    public HelloAppender(String name) {
        this.greeting = 'hello ' + name + '!\n';
    }

    public void appendTo(Appendable app) throws IOException {
        app.append(greeting);
    }
}

HelloAppender类绝对是不可变的。 方法appendTo接受Appendable 。 由于Appendable不能保证是线程安全的(例如StringBuilder ),因此附加到此Appendable会在多线程环境中引起问题。

结论

在某些情况下,创建不可变的对象绝对是一个好习惯,这对创建线程安全的代码有很大帮助。 但是当我在任何地方阅读时,我都感到困扰。 不可变对象是线程安全的 ,显示为公理。 我明白了这一点,但是我认为思考一下这总是很好,以便了解导致非线程安全代码的原因。

感谢Jose的评论,在本文的结尾我得出了不同的结论。 这都是关于不可变的定义。 需要澄清!

如果满足以下条件,则对象是不可变的:

  • 它的所有字段在使用之前都已初始化(这意味着您可以进行延迟初始化)
  • 字段的状态在初始化后不会更改(不更改表示对象图不会更改,即使子级的内部状态也是如此)

除非对象必须处理非线程安全的对象,否则不可变对象将始终是线程安全的。

参考: 不变性真的意味着线程安全吗? 从我们的JCG合作伙伴 Tibo Delor在InvalidCodeException博客中获得。


翻译自: https://www.javacodegeeks.com/2012/09/does-immutability-really-means-thread.html

业务的可变性和不可变性分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值