Java - 不可变阵列线程安全 [英] Java - Immutable array thread-safety

查看:117
本文介绍了Java - 不可变阵列线程安全的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个关于Java内存模型的问题。下面是一个简单的类,表示问题:

  public class ImmutableIntArray {

private final int [] array;

public ImmutableIntArray(){
array = new int [10];
for(int i = 0; i <10; i ++){
array [i] = i;
}
}

//总是返回正确的值?
public int get(int index){
return array [index];
}

}

据我所知,JMM保证最终字段的值在构造之后对其他线程可见。但是我想确保其他线程在构造之后会看到存储在数组中的最新版本的数据。



当然上面的代码只是一个简单的例子,问题,实际上我想为直接字节缓冲区实现一个简单的缓存,我不想依赖一些Collection类。目前我使用ReentrantReadWriteLock来确保正确的行为,但如果可能的话,我想避免它。

解决方案

,一切都会很好(嗯,让我们暂停一下判断)。在线程安全性方面,不可变性是ambrosia - 如果值不能改变,大多数并发问题立即不再是一个问题。



Amir 提到了 volatile ,这通常很有用 - 但是构造函数也有类似的语义, code> final 变量,以确保可见性。请参见 JLS条款17.5 细节 - 基本上构造函数在写入最终变量和任何后续读取之间形成发生先于关系。



/ strong>:所以你在构造函数中设置 参数的数组,它在所有线程在该点可见,然后它不会改变。所以我们知道所有其他线程将看到相同的数组。但是数组的内容是什么呢?



由于数组元素没有任何关于波动性的特殊语义,如果你刚刚声明一个类自己像:

  public class ArrayTen {
private int _0;
private int _1;
// ...
private int _9;

public int get(int index){
if(index == 0)return _0;
//等
}
}

线程只会看到这些变量,如果我们可以做一些事情来建立 happens-before 关系。如果我的理解是正确的,这需要你的原始代码的一个小的改变。



我们已经知道数组引用的设置 em>构造函数的结尾。另一个总是为真的附加点是,在同一个线程中,一个线程中的动作发生在之后的动作。因此,我们可以通过首先设置数组字段,然后分配最终字段来组合这些,以便获得可见性的传递保证。这当然需要一个临时变量:

  public class ImmutableIntArray {

private final int []数组;

public ImmutableIntArray(){
int [] tmp = new int [10];
for(int i = 0; i <10; i ++){
tmp [i] = i;
}
array = tmp;
}

// get()等
}


$ b b

我认为这是安全的,现在我们改变了看似无关紧要的任务和人口顺序。



但是,否则我错过了这意味着并发性保证不如希望的那么强大。这个问题在我看来是一个很好的例子,为什么编写防弹的多线程代码是棘手的,即使你认为你在做一些非常简单的事情,以及如何需要很多的思想和谨慎(然后bug修正)。 / p>

I have a question regarding the Java Memory Model. Here is a simple class presenting the problem:

public class ImmutableIntArray {

    private final int[] array;

    public ImmutableIntArray() {
        array = new int[10];
        for (int i = 0; i < 10; i++) {
            array[i] = i;
        }
    }

    // Will always return the correct value?
    public int get(int index) {
        return array[index];
    }

}

As far as I know the JMM guarantees that the value of final fields will be visible to other threads after construction. But I want to ensure that other threads will see the most recent version of data stored in the array after construction.

Of course the code above is just a simple example presenting the problem, actually I want to implement a simple cache for direct byte buffers and I wouldn't like to rely on some Collection classes. Currently I am using a ReentrantReadWriteLock to ensure the correct behaviour but I would like avoid it if it is possible.

解决方案

In this example, everything will be fine (hmm, let's suspend judgement a bit). Immutability is ambrosia when it comes to thread-safety - if a value cannot change, the majority of concurrency problems are immediately no longer a concern.

Amir mentioned volatile which is generally useful - but the constructor also has similar semantics for final variables that ensure visibility. See JLS clause 17.5 for details - essentially the constructor forms a happens-before relationship between the write to the final variables and any subsequent reads.

EDIT: So you set the values reference to the array in the constructor, it's visible across all threads at that point, and then it doesn't change. So we know all other threads will see the same array. But what about the array's contents?

As it stands, array elements don't have any special semantics with regard to volatility, they're as if you just declared a class yourself something like:

public class ArrayTen {
    private int _0;
    private int _1;
    // ...
    private int _9;

    public int get(int index) {
       if (index == 0) return _0;
       // etc.
    }
}

So - another thread will only see these variables if we can do something to establish the happens-before relationship. And if my understanding is correct this requires but a small change to your original code.

We already know that the setting of the array reference happens-before the end of the constructor. An additional point which is always true, is that actions in one thread happen-before later actions in that same thread. So we can combine these by setting the array fields first, and then assigning the final field, so as to get this transitive guarantee of visibility. This will of course require a temporary variable:

public class ImmutableIntArray {

    private final int[] array;

    public ImmutableIntArray() {
        int[] tmp = new int[10];
        for (int i = 0; i < 10; i++) {
            tmp[i] = i;
        }
        array = tmp;
    }

    // get() etc.
}

I think this is guaranteed to be safe, now that we've switched the seemingly irrelevant order of assignment and population.

But again, there might be something else I've missed which means the concurrency guarantees aren't as robust as hoped. This question is to my mind an excellent example of why writing bulletproof multithreaded code is tricky, even when you think you're doing something very simple, and how it takes a lot of thought and caution (and then bugfixes) to get right.

这篇关于Java - 不可变阵列线程安全的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆