Varargs,不可变数组和线程安全

时间:2011-11-19 18:55:18

标签: java thread-safety

大家好我有一个不可变的数组实现,如下所示:

public static final class FixedArray<T> {
    private final T[] array;
    public final int Length;

    @SafeVarargs
    public FixedArray(T... args) {
        array = args;
        Length = args.length;
    }

    public T Get(int index) {
        return array[index];
    }
}

public static final class FixedIntArray {
    private final int[] array;
    public final int Length;

    public FixedIntArray(int... args) {
        array = args;
        Length = args.length;
    }

    public int Get(int index) {
        return array[index];
    }
}

public static final class FixedLongArray {
    private final long[] array;
    public final int Length;

    public FixedLongArray(long... args) {
        array = args;
        Length = args.length;
    }

    public long Get(int index) {
        return array[index];
    }
}

最初我认为它保证是线程安全的。但在阅读the discussion关于不可变数组和Java内存模型后,我相信,我无法确定。

我没有使用防御性副本,与调用代码“做正确的事”的合同(和往常一样,如果它不遵循合同,则行为未定义)。

调用方法如下所示:

public static void main(String args[]) {
    int[] ints = new int[10000];
    FixedIntArray fixed_ints = new FixedIntArray(ints);
    SendToThreadA(fixed_ints);
    SendToThreadB(fixed_ints);
    SendToThreadC(fixed_ints);
    SendToThreadD(fixed_ints);
    //caller (which is this method) does the right thing, ints goes out of scope without anyone trying to modify it.
}

我想知道上面的代码是否保证是线程安全的?

4 个答案:

答案 0 :(得分:3)

由于我们不知道存储引用的数组(及其值)会发生什么,我认为如果构造函数创建参数数组的副本并设置内部最终引用,则类会更安全到复制的数组。

答案 1 :(得分:1)

鉴于您可以将数组传递给varargs方法,您需要复制构造函数输入以确保它不能在类外部进行修改。完成后,只要在复制数组中分配了所有值之后不分配final字段,就应该没问题,因为final字段的赋值保证会发生在从另一个线程读取该字段之前。

所以构造函数看起来像:

array = Arrays.copyOf(args, args.length);

Orrrr你可以使用Guava ImmutableList并获得更多权力。

答案 2 :(得分:1)

没关系。您可以要求呼叫者将阵列“切换”给您。如有必要,来电者可以克隆一个。

内存写入通常是程序中最昂贵的东西(没有外部IO)。

并非所有人都是愚蠢的。你只需要足够的防御来保护你的目标用户群。

答案 3 :(得分:0)

我不确定检查线程安全是否有意义,因为它甚至缺少更基本的安全级别。考虑这种方法:

public static void main(final String... args)
{
    final int[] arr = new int[] { 3, 3, 3 };

    final FixedIntArray threeThrees = new FixedIntArray(arr);

    System.out.println(threeThrees.Get(0)); // prints "3"
    System.out.println(threeThrees.Get(1)); // prints "3"
    System.out.println(threeThrees.Get(2)); // prints "3"

    arr[0] = arr[1] = arr[2] = 4;

    System.out.println(threeThrees.Get(0)); // prints "4"
    System.out.println(threeThrees.Get(1)); // prints "4"
    System.out.println(threeThrees.Get(2)); // prints "4"
}

问题在于,当采用int...(或Object...long...或其他任何内容)的方法时,它可以接收 一个数组由编译器隐式构造(如果你键入new FixedIntArray(3,3,3)会发生的话),一个由调用代码显式传入的数组(如上所述)。在后一种情况下,调用代码可以继续修改它传入的数组!