I will try to briefly explain a thread locking concept which I came up with using an example. Consider the following example program.
public class Main {
public static void main(String[] args) {
Data data = new Data();
while (true) {
doStuff();
doStuff();
for (int i = 0; i < 256; i++) {
System.out.println("Data " + i + ": " + data.get(i));
}
doStuff();
doStuff();
for (int i = 0; i < 256; i++) {
data.set(i, (byte) (data.get(i) + 1));
}
doStuff();
doStuff();
}
}
public static void doStuff() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Data {
private final byte[] data = new byte[256];
public byte get(int i) {
return data[i];
}
public void set(int i, byte data) {
this.data[i] = data;
}
}
It is important that only the main thread modifies data
. Now I want to make the loop which prints data
asynchronous.
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Data data = new Data();
while (true) {
doStuff();
doStuff();
executorService.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 256; i++) {
System.out.println("Data " + i + ": " + data.get(i));
}
}
});
doStuff();
doStuff();
for (int i = 0; i < 256; i++) {
data.set(i, (byte) (data.get(i) + 1));
}
doStuff();
doStuff();
}
}
After submitting the task to the executorService
the main thread can now move on working as desired. The problem is, that the main thread can potentially reach the point where it modifies data
before it was printed but the state of data
should be printed when it was submitted.
I know in this case I can create a copy of data
before submitting which is printed but that's really not what I want to do. Keep in mind that this is just an example and copying could be an expensive operation in the real code.
This is the solution I came up with for this problem.
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Data data = new Data();
Lock lock = new Lock(); // <---------------
while (true) {
doStuff();
doStuff();
lock.lock(); // <---------------
executorService.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 256; i++) {
System.out.println("Data " + i + ": " + data.get(i));
}
lock.unlock(); // <---------------
}
});
doStuff();
doStuff();
lock.waitUntilUnlock(); // <---------------
for (int i = 0; i < 256; i++) {
data.set(i, (byte) (data.get(i) + 1));
}
doStuff();
doStuff();
}
}
public class Lock {
private final AtomicInteger lockCount = new AtomicInteger();
public void lock() {
lockCount.incrementAndGet();
}
public synchronized void unlock() {
lockCount.decrementAndGet();
notifyAll();
}
public synchronized void waitUntilUnlock() {
while (lockCount.get() > 0) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
}
Now the main thread can move on working on other stuff after submitting data
. At least it can until it reaches the point where it modifies data
.
The question: Is this a good or a bad design? Or is there a better (already existing) implementation for this problem?
Note that ReentrantLock
wont work in this case. I have to lock before submitting on the main thread and release the lock on the executor thread.