My question today is regarding multidimensional arrays, and synchronization.. I'm working on a client/server project and I'm not sure about the theory of an idea I came up with:

I have an array: Object[][] myArray = new Object[256][]; It is an array of arrays. The length of myArray is 256 and will never change from 256. However, the length of the second level of arrays is dynamic, and each second-level array can be null, zero-length, or any other length.

What I need to do, since multiple threads work with myArray simutaneously, is synchronize the array during possible changes to the second-level arrays. Anywhere an access/modification is made, myArrays is synchronized. synchronized (myArray) { ... } The problem is that synchronize causes threads to block, even when they wish to read or write to a different second-level array than the one already being processed. What I want to do is lock only the specific, second-level array, and leave the rest of the arrays unlocked so the program can function more quickly.

Can I do this? synchronized (myArray[indexToLock]) { ... } I can ensure that the second-level arrays are never null, rather zero-length or more. NetBeans lets me write the code, but to test it...

Any thoughts? Thank youuu! ;)

Dani AI

Generated

As discovered, synchronizing on an inner-array object does exactly what you observed: the monitor is tied to that particular object, so threads locking different inner-array objects run concurrently. That is correct Java behavior. Important caveats, though: if the inner-array object can be replaced, set to null, or shared accidentally across slots, the monitor semantics change and you can get races or unexpected contention.

Safer, simple patterns:

  • Create a dedicated, private, final lock object per slot and never replace it. This keeps the lock identity stable even if you replace the slot's array.
  • If you have many readers and few writers, use a per-slot ReadWriteLock to allow concurrent reads.
  • If you need non-blocking reads, consider immutable inner arrays and publish replacements atomically (AtomicReferenceArray or volatile references), or use CopyOnWrite-style structures.

Example (pattern only):

final Object[] slotLocks = new Object[256];
for (int i = 0; i < slotLocks.length; i++) slotLocks[i] = new Object();

// reading
synchronized (slotLocks[index]) {
    Object[] snapshot = myArray[index];
    // safe read of snapshot
}

// writing
synchronized (slotLocks[index]) {
    myArray[index] = newInnerArray;
}

Or use ReentrantReadWriteLock per slot for reader/writer separation:

final ReentrantReadWriteLock[] locks = new ReentrantReadWriteLock[256];
// initialize each lock...
locks[idx].readLock().lock(); try { /* read */ } finally { locks[idx].readLock().unlock(); }
locks[idx].writeLock().lock(); try { /* write */ } finally { locks[idx].writeLock().unlock(); }

Troubleshooting tips: never expose the lock objects publicly; initialize locks to non-null; avoid acquiring multiple slot locks in arbitrary order (use a fixed order or tryLock with timeout to prevent deadlocks). For your use case the per-slot lock-array is the simplest, lowest-overhead fix and avoids the pitfalls of synchronizing on replaceable objects.

Alright!

I love answering my own questions. I hope that someone can learn something from this!

I conducted two tests. The first test locks the entire myArray array. The seconds locks a specific, second-level array. Both tests were monitored by myself.

Test 1

public class TestingMain {
    
    public static void main(String[] args) {
        new TestingMain();
    }
    
    // =========================
    
    Object[][] myArray = new Object[256][0];
    
    public TestingMain() {
        
        System.out.println("Starting thread...");
        
        // Initialize a thread that will share the resource
        new TestThread().start();
        
        System.out.println("Thread started.");
        
        try {
            System.out.println("Main going to sleep for 1 second.");
            Thread.sleep(1000);
            System.out.println("Main done sleeping.");
        } catch (Exception ex) {
            System.out.println("Main: " + ex);
        }
        
        synchronized (myArray) {
            System.out.println ("And we now have access to myArray!");
        }
        
        System.exit(0);
        
    }
    
    class TestThread extends Thread {
        public void run() {
            synchronized (myArray) {
                // Lock myArray for FIVE seconds!
                try {
                    System.out.println("Thread going to sleep for 5 seconds.");
                    this.sleep(5000);
                    System.out.println("Thread done sleeping.");
                } catch (Exception ex) {
                    System.out.println("Thread: " + ex);
                }
            }
        }
    }
    
}

Results of Test 1
run:
Starting thread...
Thread started.
Main going to sleep for 1 second.
Thread going to sleep for 5 seconds.
(1 second passes)
Main done sleeping.
(4 seconds pass)
Thread done sleeping.
And we now have access to myArray!


Great! The lock worked just as it should have worked. Now, let's be more specific:

Test 2

public class TestingMain {
    
    public static void main(String[] args) {
        new TestingMain();
    }
    
    // =========================
    
    Object[][] myArray = new Object[256][0];
    
    public TestingMain() {
        
        System.out.println("Starting thread...");
        
        // Initialize a thread that will share the resource
        new TestThread().start();
        
        System.out.println("Thread started.");
        
        try {
            System.out.println("Main going to sleep for 1 second.");
            Thread.sleep(1000);
            System.out.println("Main done sleeping.");
        } catch (Exception ex) {
            System.out.println("Main: " + ex);
        }
        
        synchronized (myArray[4]) {
            System.out.println ("And we now have access to myArray[4]!");
        }
        
        synchronized (myArray[123]) {
            System.out.println ("And we now have access to myArray[123]!");
        }
        
        System.exit(0);
        
    }
    
    class TestThread extends Thread {
        public void run() {
            synchronized (myArray[123]) {
                // Lock myArray[123] for FIVE seconds!
                try {
                    System.out.println("Thread going to sleep for 5 seconds.");
                    this.sleep(5000);
                    System.out.println("Thread done sleeping.");
                } catch (Exception ex) {
                    System.out.println("Thread: " + ex);
                }
            }
        }
    }
    
}

Results of Test 2
run:
Starting thread...
Thread going to sleep for 5 seconds.
Thread started.
Main going to sleep for 1 second.
(1 second passes)
Main done sleeping.
And we now have access to myArray[4]!
(4 seconds pass)
Thread done sleeping.
And we now have access to myArray[123]!

Excellent! From that I can see my program had immediate access to myArray[4], even though myArray[123] was locked by the second thread. To access myArray[123], however, the main thread had to wait until the object was unlocked. That's exactly what I wanted to know!

:cheesy:

commented: Great job, keep up going +3
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.