简体   繁体   中英

Locking of 2-Dimensional Integer Array

I have the following class and I want to make the set2Int-method thread-safe. But there should be no lock on the method (with adding synchronize). If the if-condition is true, there should be a lock on both Integers in the array (to avoid deadlocks). It should also be possible to call the method concurrently that means a lock on the whole array is also no desired solution.

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

Using locks for every integer might consume some memory. I'd use int[] array instead of Integer, and go with lock striping. This means you can keep lesser amount of locks and assign every array cell to one of that locks.

Beside that, I would not lock array cell for reading, use volatile read instead. Here is getOpaque is used, which provides no synchronization guarantess, but reads value from memory, and JIT won't optimize it away.

There might be some place for another optimizations. If you use 64-bit CPU, then you may want use 64-bit CAS in order to set both array[i][j] and array[i][j + 1] , as those elements are placed within same 64-bit word.

Here is on of the possible implementations for java 9. If you use java 8 or lower, you can use 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;
    }
}

UPDATE :

Would synchronize(array[i][j]){ synchronize(array[i][j+1]){ synchronize(array[i+1][j]}} be an thread-safe solution (inserted before the first if)? That will be thread-safe, I believe. However, locking for reading of every element might be slow.

Beside that, java uses the same java.lang.Integer objects for some int values. This means that if you have an array and every element is set to, let's say, 1, then every element will be the same object! And the synchronization will be slow, as you synchronize on the same object.

This code prints the same value, because every object is the same. Anyway, you might want to test both implementations and see what's faster.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM