2

I am new to multi-threading in Java and don't quite understand what's going on.

From online tutorials and lecture notes, I know that the synchronized block, which must be applied to a non-null object, ensures that only one thread can execute that block of code. Since an array is an object in Java, synchronize can be applied to it. Further, if the array stores objects, I should be able to synchronize each element of the array too.

My program has several threads updated an array of numbers, hence I created an array of Long objects:

synchronized (grid[arrayIndex]){
    grid[arrayIndex] += a.getNumber();
}

This code sits inside the run() method of the thread class which I have extended. The array, grid, is shared by all of my threads. However, this does not return the correct results while running the same program on one thread does.

4 Answers 4

7

This will not work. It is important to realize that grid[arrayIndex] += ... is actually replacing the element in the grid with a new object. This means that you are synchronizing on an object in the array and then immediately replacing the object with another in the array. This will cause other threads to lock on a different object so they won't block. You must lock on a constant object.

You can instead lock on the entire array object, if it is never replaced with another array object:

synchronized (grid) {
    // this changes the object to another Long so can't be used to lock
    grid[arrayIndex] += a.getNumber();
}

This is one of the reasons why it is a good pattern to lock on a final object. See this answer with more details:

Why is it not a good practice to synchronize on Boolean?

Sign up to request clarification or add additional context in comments.

14 Comments

You are right but it might be overkill to lock the whole array. You could set up a sibling array with lock objects final Object[] locks = new Object[arrayLength]; for (int i = 0; i< arrayLength; i++) { locks[i] = new Object(); } then synchronized (locks[arrayIndex]) { grid[arrayIndex] += a.getNumber(); }
@ashirley It's worth noting that the array should never have its elements modified. It should be initialized, filled, and then never changed again. Unfortunately, declaring it as final isn't enough to guarantee that its inner elements won't change.
The OP is only protecting an array assignment @ashirley. The lock granularity does not matter in that case.
@Brian yeah, better than nothing though
@Gray true, but worth pointing out incase it evolves into a more lengthy operation
|
2

Another option would be to use an array of AtomicLong objects, and use their addAndGet() or getAndAdd() method. You wouldn't need synchronization to increment your objects, and multiple objects could be incremented concurrently.

1 Comment

Good one @JB. Of course you still pay the price for the memory barrier.
1

The java class Long is immutable, you cannot change its value. So when you perform an action:

grid[arrayIndex] += a.getNumber();

it is not changing the value of grid[arrayIndex], which you are locking on, but is actually creating a new Long object and setting its value to the old value plus a.getNumber. So you will end up with different threads synchronizing on different objects, which leads to the results you are seeing

Comments

0

The synchronized block you have here is no good. When you synchronize on the array element, which is presumably a number, you're synchronizing only on that object. When you reassign the element of the array to a different object than the one you started with, the synchronization is no longer on the correct object and other threads will be able to access that index.

One of these two options would be more correct:

private final int[] grid = new int[10];

synchronized (grid) {
    grid[arrayIndex] += a.getNumber();
}

If grid can't be final:

private final Object MUTEX = new Object();

synchronized (MUTEX) {
    grid[arrayIndex] += a.getNumber();
}

If you use the second option and grid is not final, any assignment to grid should also be synchronized.

synchronized (MUTEX) {
    grid = new int[20];
}

Always synchronize on something final, always synchronize on both access and modification, and once you have that down, you can start looking into other locking mechanisms, such as Lock, ReadWriteLock, and Semaphore. These can provide more complex locking mechanisms than synchronization that is better for scenarios where Java's default synchronization alone isn't enough, such as locking data in a high-throughput system (read/write locking) or locking in resource pools (counting semaphores).

10 Comments

I disagree with the last paragraph. Those locks require a lot more careful programming (try/finally) so cannot be called "safer" nor "more predictable" nor do they "avoid a lot of problems". The opposite is true. More powerful and in certain situations better performance, yes.
Also in the first, you don't synchronize on a number, you synchronize on an object. You also use the word value which is misleading.
@Gray I'll concede on the word safe and remove it. As long as you're following the proper patterns for any synchronization, it'll be safe. As for predictable, native Java synchronization is unpredictable. The thread that will receive the monitor if two or more threads is waiting on it is random. With other systems, I can turn fairness on and make it queue threads and give the lock first-come-first-serve instead of random order, preventing things like thread starvation. As per your second comment, I agree, the wording has been changed.
Interesting RE: predictability but I suspect this is an academic issue wrt to the language definition @Brian. Do you know of any situations where this is true in practice? I think it is extremely dangerous to go around using Lock instead of synchronized for this reason. Especially when you are counseling newbie thread programmers on SO.
@Gray As far as I understand it, the first thread that the scheduler wakes up that is blocking on the monitor is the one that gets it, so in practice, it's possible if the processor scheduler's algorithm wakes the threads in an order that always puts one of the threads behind the others. I guess in a more practical sense, most systems (Windows and *NIX) have good scheduling algorithms that would likely circumvent more serious starvation issues. But it's nice to have the guarantee when working with systems where you want locks served FIFO, like in time-sensitive request/response systems.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.