我有以下类,我想使set2Int方法线程安全。但是该方法应该没有锁定(添加同步)。如果if-condition为true,则应该对数组中的两个Integers都进行锁定(以避免死锁)。也应该可以同时调用该方法,这意味着对整个阵列的锁定也不是所希望的解决方案。
public class Container {
private Integer[][] array;
public Container(int xs, int ys){
array = new Integer[ys][];
for (int i = 0; i < xs; i++){
array[i] = new Integer[xs];
}
}
public void set2Int(int a, int b){
for(int i = 0; i < array.length - 1; i++){
for(int j = 0; j < array[i].length - 1; j++){
if(array[i][j] == 0 && array[i][j+1] == 0){
array[i][j] = a;
array[i][j+1] = b;
return;
}
if(array[i][j] == 0 && array[i+1][j] == 0){
array[i][j] = a;
array[i+1][j] = b;
return;
}
}
}
}
}
对每个整数使用锁可能会占用一些内存。我使用int[]
数组而不是Integer,并使用锁定条带化。这意味着您可以保留较少量的锁并将每个阵列单元分配给其中一个锁。
除此之外,我不会锁定阵列单元格进行读取,而是使用volatile read
。这里使用的是getOpaque
,它不提供同步保证,但是从内存读取值,JIT不会优化它。
可能有一些地方可以进行其他优化。如果使用64位CPU,则可能需要使用64位CAS来设置array[i][j]
和array[i][j + 1]
,因为这些元素放在相同的64位字内。
以下是java 9的可能实现。如果使用java 8或更低版本,则可以使用AtomicIntegerArray
。
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Container {
private static final int LOCK_GRANULARITY = 128;
private static final VarHandle AA = MethodHandles.arrayElementVarHandle(int[].class);
private final int[][] array;
private Lock[] locks = new Lock[LOCK_GRANULARITY];
public Container(int xs, int ys){
array = new int[ys][];
for (int i = 0; i < ys; i++){
array[i] = new int[xs];
}
locks = new Lock[LOCK_GRANULARITY];
for(int i = 0; i < locks.length; i++) {
locks[i] = new ReentrantLock(false);
}
}
public void set2Int(int a, int b){
for(int i = 0; i < array.length - 1; i++){
int[] iArray = array[i];
int[] iNextArray = array[i + 1];
for(int j = 0; j < array[i].length - 1; j++){
if((int)AA.getOpaque(iArray, j) == 0 && (int)AA.getOpaque(iArray, j + 1) == 0){
if(update(i, j, a, i, j + 1, b)) {
return;
}
}
if((int)AA.getOpaque(iArray, j) == 0 && (int)AA.getOpaque(iNextArray, j) == 0){
if(update(i, j, a, i, j + 1, b)) {
return;
}
}
}
}
}
private boolean update(int i1, int j1, int v1, int i2, int j2, int v2) {
lock(i1, j1);
lock(i2, j2);
try {
if(array[i1][j1] == 0 && array[i2][j2] == 0) {
array[i1][j1] = v1;
array[i2][j2] = v2;
return true;
} else {
return false;
}
} finally {
unlock(i2, j2);
unlock(i1, j1);
}
}
private void lock(int i, int j) {
locks[Math.abs(hash(i, j) % locks.length)].lock();
}
private void unlock(int i, int j) {
locks[Math.abs(hash(i, j) % locks.length)].unlock();
}
private int hash(int i, int j) {
//TODO: choose good hash!
return 31 * (i + 31 * j);
}
public int[][] getArray() {
return array;
}
}
更新:
同步(array [i] [j]){synchronize(array [i] [j + 1]){synchronize(array [i + 1] [j]}}是一个线程安全的解决方案(在第一个之前插入) )?我认为这将是线程安全的。但是,锁定读取每个元素可能会很慢。
除此之外,java对某些int值使用相同的java.lang.Integer
对象。这意味着如果你有一个数组并且每个元素都设置为1,那么每个元素将是同一个对象!当您在同一个对象上进行同步时,同步速度会很慢。
此代码打印相同的值,因为每个对象都是相同的。无论如何,您可能想要测试两个实现,看看哪个更快。
Integer[] array = new Integer[128];
for(int i = 0; i < array.length; i++) {
array[i] = 5;
}
for(int i = 0; i < array.length; i++) {
System.out.println(System.identityHashCode(array[i]));
}