2

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.

stonar96
  • 1,359
  • 2
  • 11
  • 39

4 Answers4

3

Java has higher-level synchronization abstractions. In general, you should really avoid wait() and notifyAll(), which are too low-level and complex to use correctly and read.

In this case, you could just use a shared blocking queue (a synchronous queue looks appropriate to me) between both threads:

    ExecutorService executorService = Executors.newSingleThreadExecutor();
    Data data = new Data();
    SynchronousQueue queue = new SynchronousQueue();

    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));
                }
                queue.put(data);
            }
        });

        doStuff();
        doStuff();

        data = queue.take();
        for (int i = 0; i < 256; i++) {
            data.set(i, (byte) (data.get(i) + 1));
        }

        doStuff();
        doStuff();
    }
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Thanks, I really like this answer. I have also read about `Semaphore`s now and I think I can also write my code using them. What do you think about them? Are they still too low-level? – stonar96 Mar 16 '19 at 17:09
  • 1
    Yes, the printing thread could release a permit and the main thread block until a permit is available. – JB Nizet Mar 16 '19 at 17:25
  • @stonar96, One way to think of a `Semaphore` is to think of it as a blocking queue of informationless _tokens_ (also sometimes known as "_permits_"). – Solomon Slow Mar 16 '19 at 22:27
  • @JBNizet, this answer could be somewhat improved by moving the `new Data()` call into the task that is submitted to the executor service. That detail might not amount to much in such a trivial example as this, but in a more complex application, making the `data` object inaccessible to the main thread until such time as it makes sense for the main thread to operate on it, could be just the thing to prevent some junior programmer from "optimizing" the code by allowing the main thread to start doing its work before the `data` is ready. – Solomon Slow Mar 16 '19 at 22:33
  • Yes, I thought about that, but I wanted to keep the same code structure as the original one to show a minimal change. Since the runnable is implemented as an anonymous class, doing what you suggest is not possible without refactoring the code. – JB Nizet Mar 16 '19 at 22:37
  • Thanks so far, I think I get it. But what I don't understand is how can I use the `Queue` or a `Semaphore` as shown in the answer, if there should be multiple submissions of the same `data` to the `executorService` possible? In this case `#poll()` returns after the first task is ready but the others are probably not. I have solved this in my `Lock` implementation by counting. – stonar96 Mar 17 '19 at 00:20
  • @stonar96 this post answers your specific question. If you have a different one, then ask a different question. The answer could be similar, or i could be different: it depends on the *other* problem you're trying to solve. – JB Nizet Mar 17 '19 at 06:58
  • 1
    @JB Nizet I have noticed that `#poll()` should be changed to `#take()`. `#poll()` just returns null if there is no element in the queue, however `#take()` waits for an element due to the Javadocs. – stonar96 Mar 18 '19 at 15:21
  • @stonar96 you're absolutely right. I fixed the post. Thanks. – JB Nizet Mar 18 '19 at 15:22
  • @JB Nizet Thanks, I think `#put(E)` is also wrong. It should not wait, so `#offer(E)` is the right choise. – stonar96 Mar 18 '19 at 16:30
  • Actually, if the goal is to not wait, a synchronous queue shouldn't be used in the first place. A LinkedBlockingQueue would be more appropriate. offer() would do nothing if no thread is waiting in the case of a SynchronousQueue (unless I'm misinterpreting the javadoc). Anyway, the point of the answer is to show one of the possible solutions, using a higher-level abstraction. Several variations can exist. – JB Nizet Mar 18 '19 at 16:37
1

You want this Data thing to be built asynchronously, and the main thread wants to be able to proceed to a certain point, then needs to get the completed object. This is what Futures are for, it gives you a reference to a computation that may not have completed yet.

Rewrite the async part as a Callable so that it returns a Data as a result.

Callable<Integer> task = () -> {
    Data data = new Data();
    for (int i = 0; i < 256; i++) {
        System.out.println("Data " + i + ": " + data.get(i));
    }
    return data;
};

ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Data> future = executor.submit(task);
doStuff();
// ... main thread goes about its business

// when you get to a point where you need data, 
// you can block here until the computation is done
Data data = future.get(); 

This way the Future handles making sure the Data object is made visible across threads.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • Thanks! I had pretty much the same idea at the same time you wrote the answer (see my answer). What do you think about my additional `Queue` to support multiple submissions? – stonar96 Mar 18 '19 at 18:03
  • 1
    @stonar96: the queue is fine, it just depends on what you're doing. it abides by the advice to use higher-level abstractions. – Nathan Hughes Mar 18 '19 at 18:15
0

I found an alternative approach to the problem which also answers my comment from the answer of @JB Nizet. ExecutorService#submit(Runnable) returns a Future<?> which can be used to wait until the task is ready. If multiple submissions should be possible one can just create a Queue<Future<?>> queue , always offer the Future<?> returned by ExecutorService#submit(Runnable) to the queue and at the point where the main thread should wait just #poll().get() the entire queue.

Edit: I have also found a related answer here: https://stackoverflow.com/a/20496115/3882565

stonar96
  • 1,359
  • 2
  • 11
  • 39
-1

This seems to be covered well enough by basic synchronization: doStuff expects to have sole access to the data, presumably so that it can modify the data as a whole safely. Meanwhile, doPrint expects to operation on stable data. In these two cases, the data is the unit of state to which access is controlled, and the appropriate locking technique is to synchronize on the data instance which is being accessed.

public void runningInThread1() {
    Data someData = getData(); // Obtain the data which this thread is using
    doStuff(someData); // Update the data
    // Looping and such omitted.
}

public void runningInThread2() {
    Data someData = getData();
    doPrint(someData); // Display the data
}

public void doStuff(Data data) {
    synchronized ( data ) {
        // Do some stuff to the data
    }
}

public void doPrint(Data data) {
    synchronized ( data ) {
        // Display the data
    }
}

Alternatively, if doStuff and doPrint are implemented as instance methods of Data, then the synchronization could be made to happen by adding the synchronized keyword to the instance methods.

Thomas Bitonti
  • 1,179
  • 7
  • 14
  • This is not covered well enough by basic synchronization. This still has the problem which I have described in my question. It doesn't guarantee the right order of print and modify. – stonar96 Mar 18 '19 at 16:27
  • The problem is, when `data` is submittet to be printed async it the code is not inside your synchronized block and until it is inside, `data` may has beed changed, which I want to avoid, since I want to print the state of data which was submitted. – stonar96 Mar 18 '19 at 17:11
  • Can you describe the sequence that is expected for doPrint and doStuff, and how these actions are scheduled? For example, do you have occasional doStuff which occurs, and which is triggered by an outside and essentially random event, while doPrint is to occur at scheduled intervals? – Thomas Bitonti Mar 18 '19 at 19:29
  • But it sounds like you are looking for something like Thread.join. See https://docs.oracle.com/javase/tutorial/essential/concurrency/join.html. But also, there are a lot samples to be found. – Thomas Bitonti Mar 18 '19 at 19:37
  • In my opinion the sequence is well described in the example codes of the question. The main thread submits `data` at any time to an `executorService` and the main thread modifies `data` at any time. Now I want to make sure that `data` is not changed from the point where the main thread submits `data` until the `executorService` is ready. Note that there is only one thread (the main thread) which submits and modifies `data`. The problem with basic synchronization is that when the main thread submits and moves on working it's not guaranteed that the synchronized `doPrint()` was already invoked. – stonar96 Mar 18 '19 at 20:41
  • That's the reason why I placed the `lock.lock()` in my example code before submitting on the main thread and `lock.unlock()` inside the `run()` method on the executor thread. With basic synchronization it would be aquivalent to have both `lock.lock()` and `lock.unlock()` inside the `run()` method. `Thread#join()` might work well for this but as said in other answers I should use higher level synchronisation and this seems pretty low level to me. Thanks for your efforts anyway! – stonar96 Mar 18 '19 at 20:50
  • Then, is the goal to allow the main thread to continue activity while doPrint continues asynchronously? (That activity being something besides updates to the data via doStuff, otherwise, there won't be anything gained by having doPrint in another thread.) In that case, synchronization is still sufficient, except that a request to print the data won't necessarily print the current data, as the print thread won't be guaranteed to run before modifications are made to the data by the main thread. – Thomas Bitonti Mar 18 '19 at 21:08
  • The question then becomes how to ensure the print operation runs before the next update to the data. – Thomas Bitonti Mar 18 '19 at 21:09
  • The operation that seems to be needed is a "yield to all print threads" (or just "yield to the print thread, if one is running") which is performed at the beginning of a call to doStuff. That would be implemented using Thread.join of the main thread to all current print threads as the first step of doStuff (or before each call to doStuff). Locks or Futures seem unnecessary and overly complex. – Thomas Bitonti Mar 18 '19 at 21:18
  • Yes, the goal is that the main thread can continue activity while doPrint continues asynchronously. The main thread can cantinue until it hits the point where `data` is modified. > _In that case, synchronization is still sufficient, except that a request to print the data won't necessarily print the current data, as the print thread won't be guaranteed to run before modifications are made to the data by the main thread._ < That's exactly what I am talking about. It should print the current data. Now the question is how should I invoke join() if I use an ExecutorService? – stonar96 Mar 18 '19 at 21:46