并发主题

不可变真的意味线程安全?

我们常看到:"如果对象是不可改变的,它是线程安全的。"但是是真的吗?

何为不可变?一个不可变对象是指一个对象一旦构建后的状态就不会改变。

看下面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发现在改变了内部状态值int hash;看其实现可以总结,不可变可以改变其内部状态,只要这种改变不要对外暴露。

我们重写一下hasCode代码如下:

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,直接基于内部状态进行改变操作。如果同时有几个线程调用这个方法,返回的值可能是不同的。

那么这个类是不可改变的吗?由于两个不同的线程可以看到不同的哈希码,在外部的角度来看,我们的状态变化,所以它并不是一成不变的。

所以我们可以得出结论,String是不可改变的,因为它是线程安全的,而不是相反。所以...如果说:"做一些不可变的对象,这样它就是线程安全的!但一定要注意,首先这个不可变对象必须是线程安全的!"

我们看看时间格式使用:

public class VerySimpleDateFormat {

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

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

这个不是线程安全的,因为formatter.format方法不是。

这是不可变的对象?好问题!我们已经尽最大努力使所有字段不能修改,我们不使用任何的setter或任何方法来讲对象的状态改变。其实,内部SimpleDateFormat自己改变了其状态,这使得它不是线程安全的。

这个例子得出结论,不容易实现一个不可变类。 final关键字是不够的,你必须确保你的对象字段不被改变其状态,有时这又是不可能的。(如果按照DDD设计成值对象就从语义上保证不可变了)

看看这段代码:

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);
    }
}

虽然有final方法,但是Appendable改变了状态,这个类不是不可变的,是可变的。

 

java多线程

Java同步或锁

Java性能调优