[2020 devnexus] synchronized is obsolete

Speaker: Enrique Zamudio

For more, seeĀ table of contents


Classes

  • AtomicInteger
    • incrementAndGet()
    • compareAndSet()
  • AtomicReference
    • updateAndGet()
  • ConcurrentHashMap – methods on Map, but happens atomically
    • computeIfAbsent()
    • merge()
  • ConcurentSkipListMap
  • ConcurrentSkipListSet
  • LinkedBlockingQueue
    • Can take elements and deliver elements concurrently.
    • Every element delivered to exaclty one consumer
    • Unblocks consumers as long as something in queue
  • Locks – Lock, ReadWriteLock, ReentrantLock, CountDownLatch, CyclicBarrier, Semaphore, Phaser
    • ReentrantLock – execute code if can acquire lock
    • lock()
    • tryLock()
    • tryLock(10, SECONDS)
    • unlock()
  • Akka – project that implements Actor paradigm
  • vert.x – project for reactive – do things in single thread
  • Project Loom – lightweight threads. I thread takes 100MB just to exist; Project Loom is more like 2KB. Also no context switching.

Multiple processes

  • multiple copies of your jar
  • multiple vms with load balancer
  • synchronized or lock fails because each process can run code at same time
  • Can use database select … for update in database transaction so outside your process.
  • Similarly can use redis to set a lock with a random value. If the value matches, you know the lock is yours. If JVM crashes, lock stays. Can add lock timeout to avoid this
  • FencedLock – developed by Hazelcast Raft consensus model.

My take

I feel like I re-learn this topic every time I read the chapter in our book. (Scott wrote the chapter). I’m glad I went to this session. The more times I hear this, the more sticks. I had forgotten the project loom info so was definitely good to hear that again. The part about multiple processes was mostly new to me. (I just new about select for update)

[2019 oracle code one] java concurrency

Java Concurrency, A(nother) Peak Under the Hood

Speakers: David Buck (Oracle) – @DavidBuckJP

For more blog posts, see The Oracle Code One table of contents


Multi threaded code use cases

  • GUIs
  • Libraries
  • Batch Processing
  • Worst case: writing multi-threaded code but don’t know you are

Problems

  • Race conditions
  • Heisenbugs/observer problem – occur in Prod (or locally), but disappear in debugger

Tools

  • Java memory model
  • synchronized keyword
  • java.util.concurrent

Memory model

  • What guarantees do you have that writes in one thread are available to another thread?
    • Local variables safe
    • Static variables/instance variables can introduce concurrency problems
    • JIT compiler can change order things are done (vs the order you code them in)
    • If optimizing compiler sees a = 1 and a=a+1, compiler will just initialize a to 2.
    • Out of order execution – hardware can execute out of order to improve performance
  • Clartify what multithreaded behavior developers may depend on. Limits what types of optimizations the runtime can do
  • Spec: https://docs.oracle.com/javase/specs/jls/se13/html/jls-17.html

Things Java Protects us from

  • Testing on Raspberry Pi because ARM so can see problems that don’t occur on Intel chips
  • memory barrier/fence
  • prevents reordering before before/after fence. How do to this varies by processor./tool chain. Need to set two – one on read and one on write to guarantee behavior for testing.
  • Types: store-store, store-load, load-store, load-load

Relativity

  • As in physics, no single “correct” timelines
  • Observed order of events depends on frame of reference
  • Each core can have slightly different view of memory
  • This means core files aren’t 100% accurate

Changes to memory model

  • Originally only dealt with synchronized – mutual exclusion and happened-before. Volatile was defined but just about visibility
  • This was a problem because volatile didn’t enforce happened-before and final values could change.
  • Java 5 adopted Doug Lea’s open source APIs (JSR-166)
  • JSR-133 – ensured volatile does establish happens-before and final values will never changed
  • Volatile != atomic
  • id++ still not thread safe on a volatile field. Can use synchronized to fix
  • In original memory model, 64-bit numbers (longs and doubles) not updated atomically

Practical advice

  • Use highest level construct available. java.util.concurrency > synchronized/wait/notify. Last choice is volatile/final
  • Don’t roll your own. Even Doug Lea didn’t get it perfect
  • Java 8 – Lambdas/streams – high level
  • Java 9 – Var handles – lower level than volatile/final. Alllows value to be volatile only when we want it to be. Explicit fence/acquire/release. This was introduced for use cases sun.misc.Unsafe used to deal with.

My take

This was really interesting. It was like a peek behind the curtain! My only complain is that dark red text on a black background is hard to read. Luckily there wasn’t much of it and it was obvious what was being “emphasized” in red. Also, I liked the demos!

The Amazon AWS Java SQS Client is *Not* Thread-safe

I recently added long polling to an Amazon SQS project that processes thousands of messages a minute. The idea was simple:

  • Spawn N number of threads (let’s say 60) that repeatedly check an SQS queue using long polling
  • Each thread waits for at most one message for maximum concurrency, restarting if no message is found
  • Each time a message is found, the thread processes it and ACK’s via deleteMessage() (failure to do so causes the message to go back on the queue after the visibility timer is reached)

For convenience, I used the Java Concurrency API ScheduledExecutorService.scheduleWithFixedDelay() method, setting each thread with 1 millisecond delay, although I could have accomplished the same thing using the Thread class and an infinite while() loop. With short polling, this kind of structure would tend thrash, but with long polling, each thread is just waiting when there are no messages available. Note: For whatever reason, Java does not allow a 0 millisecond delay for this method, so 1 millisecond it is!

Noticing the Problem
When I started testing my new version based on long polling, I noticed something quite odd. While the messages all seem to be processed quickly (1-10 milliseconds) and there were no errors in the logs, the AWS Console showed 50+ messages in-flight. Based on the number of messages being processed a second and the time it was taking to process them, the in-flight counter should have been only 3-4 messages at any given time but it consistently stayed high.

Isolating the Issue
I knew it had something to do with long polling, since previously with short polling I never saw that many messages consistently in flight, but it took a long time to isolate the bug. I discovered that in certain circumstances the Amazon AWS Java SQS Client is not thread-safe. Apparently, the deleteMessage() call can block if too many other threads are performing long polling. For example, if you set the long polling to 10 seconds, the deleteMessage() can block for 10 seconds. If you set long polling to 20 seconds, the deleteMessage() can block for 20 seconds, and so on. Below is a sample class which reproduces the issue. You may have to run it multiple times and/or increase the number of polling threads, but you should see intermittent delays in deleting messages between Lines 25 and 27.

package net.selikoff.aws;

import java.util.concurrent.*;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.sqs.*;
import com.amazonaws.services.sqs.model.*;

public class SQSThreadSafeIssue {
	private final String queueName;
	private final AmazonSQS sqsClient;
	private final int numberOfThreads;
	
	public SQSThreadSafeIssue(Regions region, String queueName, int numberOfThreads) {
		super();
		this.queueName = queueName;
		this.sqsClient = AmazonSQSClientBuilder.standard().withRegion(region).build(); // Relies on locally available AWS creds
		this.numberOfThreads = numberOfThreads;
	}
	
	private void readAndProcessMessages(ReceiveMessageRequest receiveMessageRequest) {
		final ReceiveMessageResult result = sqsClient.receiveMessage(receiveMessageRequest);
		if(result!=null && result.getMessages()!=null && result.getMessages().size()>0) {
			result.getMessages().forEach(m -> {
				final long start = System.currentTimeMillis();
				System.out.println("1: Message read from queue");
				sqsClient.deleteMessage(new DeleteMessageRequest(queueName, m.getReceiptHandle()));
				System.out.println("2: Message deleted from queue in "+(System.currentTimeMillis()-start)+" milliseconds");
			});
		}
	}
	
	private void createMessages(int count) {
		for(int i=0; i<count; i++) {
			sqsClient.sendMessage(queueName, "test "+System.currentTimeMillis());
		}
	}
	
	public void produceThreadSafeProblem(int numberOfMessagesToAdd) {
		// Start up and add some messages to the queue
		createMessages(numberOfMessagesToAdd);
		
		// Create thread executor service
		final ScheduledExecutorService queueManagerService = Executors.newScheduledThreadPool(numberOfThreads);
		
		// Create reusable request object with 20 second long polling
		final ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest();
		receiveMessageRequest.setQueueUrl(queueName);
		receiveMessageRequest.setMaxNumberOfMessages(1);
		receiveMessageRequest.setWaitTimeSeconds(20);
		
		// Schedule some thread processors
		for(int i=0; i<numberOfThreads; i++) {
			queueManagerService.scheduleWithFixedDelay(() -> readAndProcessMessages(receiveMessageRequest),0,1,TimeUnit.MILLISECONDS);
		}
	}
	
	public static void main(String[] args) {
		final SQSThreadSafeIssue issue = new SQSThreadSafeIssue(Regions.YOUR_REGION_HERE,"YOUR_QUEUE_NAME_HERE",60);
		issue.produceThreadSafeProblem(5);
	}
}

And below is a sample output of this, showing that each message took 20 seconds (the long polling time) to be deleted.

1: Message read from queue
1: Message read from queue
1: Message read from queue
1: Message read from queue
1: Message read from queue
2: Message deleted from queue in 20059 milliseconds
2: Message deleted from queue in 20098 milliseconds
2: Message deleted from queue in 20024 milliseconds
2: Message deleted from queue in 20035 milliseconds
2: Message deleted from queue in 20038 milliseconds

Note: The SQSThreadSafeIssue class requires Java 8 or higher along with the following libraries to compile and run. It uses the latest version of the Amazon AWS Java SDK 1.11.278 available from AWS (although not in mvnrepository.com yet):

Understanding the Problem
Now that we see messages are taking 20 seconds (the long polling time) to be deleted, the large number of messages in-flight makes total sense. If the messages are taking 20 seconds to be deleted, what we are seeing is the total number of in-flight messages over the last 20 second window waiting to be deleted, which is not a ‘true measure’ of in-flight messages actually being processed. The more threads you add, say 100-200, the more easily the issue becomes to reproduce. What’s especially interesting is that the polling threads don’t seem to be blocking each other. For example, if 50 messages come in at once and there are 100 threads available, then all 50 messages get read immediately, while not a single deleteMessage() is allowed through.

So where does the Problem lie? That’s easy. Despite being advertised as @ThreadSafe in the API documentation, the AmazonSQS client is certainly not thread-safe and appears to have a maximum number of connections available. While I imagine this doesn’t come up often when using the default short-polling, it is not difficult to reproduce this problem when long-polling is enabled in a multi-threaded environment.

Finding a Solution
The solution? Oh, that’s trivial. So trivial, I was tempted to leave as an exercise to the reader! But since I’m hoping AWS developers will read article and fully understand the bug, so they can apply a patch, here goes….

You just need to create two AmazonSQS instances in the constructor of SQSThreadSafeIssue, one for reading (Line 21) and one for deleting (Line 26). Once you have two distinct clients, the deletes all happen within a few milliseconds. Once applied to the original project I was working on, the number of in-flight messages dropped significantly to a number that was far more expected.

Although this work-around fix is easy to apply, it should not be necessary, aka you should be able to reuse the same client. In fact, AWS documentation often encourages you to do so. The fact that the Amazon SQS client is not thread-safe when long polling is enabled is a very serious issue, one I’m hoping AWS will resolve in a timely manner!