Does this custom Java sync pattern work?

Let's say I have two threads that work as follows:

  • Thread A, which performs calculations when updating pixels of a common image
  • Thread B periodically reads the image and copies it to the screen.

Thread A quickly does the work, say, 1 million updates per second, so I suspect it would be a bad idea to lock and unlock the lock / mutex / monitor, which is often. But if there is no blockage and there is no way to establish a connection between the past and the stream from stream A to stream B, then according to the Java memory model (JMM specification), thread B is not guaranteed at all to see any of the updates A for the image.

So, I thought that the minimum solution for threads A and B to synchronize simultaneously with the same common lock, but actually does not do any work inside the synchronized block - this is what makes the template non-standard and dubious, To illustrate in a semi-real semi-pseudo-code:

class ComputationCanvas extends java.awt.Canvas { private Object lock = new Object(); private int[] pixels = new int[1000000]; public ComputationCanvas() { new Thread(this::runThreadA).start(); new Thread(this::runThreadB).start(); } private void runThreadA() { while (true) { for (1000 steps) { update pixels directly without synchornization } synchronized(lock) {} // Blank } } private void runThreadB() { while (true) { Thread.sleep(100); synchronized(lock) {} // Blank this.repaint(); } } @Override public void paint(Graphics g) { g.drawImage(pixels, 0, 0); } } 

Is adding empty synchronization blocks in such a way as to properly achieve the effect of transferring data from stream A to stream B? Or is there another solution that I could not imagine?

+7
java multithreading synchronization java-memory-model happens-before
source share
1 answer

Yes it works. But it works horribly.

It happens before it works only when the release of the writer occurs before the acquisition of the reader. In your implementation, it is assumed that everything you write will be completed before further reading / updating with ThreadB . Due to the fact that your data will be red all the time synchronized, this will cause performance problems, although, as far as I can not say for sure. Of course, you made your synchronization more granular, have you tested it already?

The best solution is to use the singleton / single-user singleton / transfer SPSC queue (single producer / single consumer) to store the current snapshot of the message flow and use it with every update.

 int[] data = ... Queue<int[]> queue = new ... // Thread A while (true) { for (1000 iterations or so) { ... } queue.add(data); } // Thread B while (true) { int[] snapshot = queue.take(); this.repaint(); } 

The advantage of this is that you do not need to do busywait, you can just wait until the queue is locked or until the next recording. You can skip entries that you do not have time to update. You do not need to depend on an arbitrary thread scheduler to schedule data flows for you.

Remember that thread-safe data structures are great for transferring data between streams.

Edit: oops, I forgot to say that depending on how your updates go, you can use a copy of the array so that your data is not distorted from random entries that are not cached.

+1
source share

All Articles