public class ConcurrentIntegerArray {
private final Lock lock = new ReentrantLock();
private final int[] arr;
public ConcurrentIntegerArray(int size) {
arr = new int[size];
}
public void set(int index, int value) {
lock.lock();
try {
arr[index] = value;
} finally {
lock.unlock();
}
}
public int get(int index) {
lock.lock();
try {
return arr[index];
} finally {
lock.unlock();
}
}
}
In this example, we have an underlying array, arr, and we wrap
accesses to this array in a lock/unlock pair. Once thing you may be wondering is
about memory access semantics. With regular synchronized blocks, the JVM
guarantees that entering and exiting the synchronized block acts as a "memory
barrier", meaning that, for example, where a memory write occurs inside the
synchronized block, this write cannot be re-ordered to occur after
the block is exited (else another thread might not see it). Here, there's no
synchronized, so how does it work? Well, it turns out that a
contract of the Lock interface is that it provides the
same memory barrier behaviour as synchronized. What that
basically means is that we can safely use the lock/unlock action as the
thing that guarnatees that a write by Thread A will be seen by Thread B.
In case it's not obvious: once we've chosen a Lock as our
method of synchronizing access to a particular variable, we should make sure
we use only that same lock object for all accesses.
In particular, we can't mix synchronization via a Lock with
synchronization via synchronized and expect it to work! I mention
this mainly because it's an accident that could happen if you are adapting
legacy code to use explicit locks instead of synchronized blocks2.
(When constructing the object,
it is OK to rely on final here to make sure
that the arr reference set by one thread is visible to other
threads accessing the get/set methods.)
Next...
Now we've seen the basics of using explicit locks, we look at some of
the benefits, starting with timed
and interruptible locking.
Notes:
1. A lock is said to be re-entrant if the owning thread can
call its lock method multiple times without blocking. To release the lock, the owning
thread must call the unlock method as many times as it called the lock method.
2. Of course, if you've used good encapsulation, all of the locking needed by
a particular class will be internal to that class and it'll be easy to
find all the places where synchronized is used, won't it...?
Java programming articles and tutorials on this site are written by Neil Coffey (@BitterCoffey).
Suggestions are always welcome if you wish to suggest topics for Java tutorials or programming articles,
or if you simply have a programming question that you would like to see answered on this site. Most topics
will be considered. But in particular, the site aims to provide tutorials and information on topics that
aren't well covered elsewhere, or on Java performance information that is poorly described or understood.
Suggestions may be made via the Javamex blog (see the site's front page for details).
Copyright © Javamex UK 2012. All rights reserved.