7 Techniques for Thread-safe Classes

Almost every Java application uses threads. Like the Tomcat Web server in a separate worker thread processing each request, even using Java. Util. Concurrent. ForkJoinPool to improve performance.

Therefore, it is necessary to write classes in a thread-safe manner, which can be achieved using the following techniques.

stateless

When multiple threads access a static variable of the same class, access to that variable must be coordinated to avoid synchronization problems.

The simplest of these is to avoid static variable access to the class. A static method in a class uses only local variables and method input parameters. Take some code from the java.lang.Math class as an example:

public static int subtractExact(int x, int y) {
    int r = x - y;
    if (((x ^ y) & (x ^ r)) < 0) {
        throw new ArithmeticException("integer overflow");
    }
    return r;
}
Copy the code

None Shared State

If you can’t avoid state, don’t share state. State should be owned by only one thread. An example of this technique is the event handling threads of SWT or Swing graphical user interface frameworks.

You can implement thread-local instance variables by inheriting the Thread class and adding instance variables. In the following example, each starting worker thread has its own pool and workQueue variables.

package java.util.concurrent;
public class ForkJoinWorkerThread extends Thread {
    final ForkJoinPool pool;                
    final ForkJoinPool.WorkQueue workQueue; 
}
Copy the code

Another way to implement thread-local variables is to use the java.lang.threadLocal class to create thread-local fields. Here is an example of an instance variable using java.lang.threadLocal:

public class CallbackState {
public static final ThreadLocal<CallbackStatePerThread> callbackStatePerThread = 
    new ThreadLocal<CallbackStatePerThread>()
   {
      @Override
        protected CallbackStatePerThread  initialValue(a)
      { 
       returngetOrCreateCallbackStatePerThread(); }}; }Copy the code

Wrap the type of the instance variable in java.lang.threadLocal. You can provide an initialValue for java.lang.threadlocal through the method initialValue().

The following shows how to use this instance variable:

CallbackStatePerThread callbackStatePerThread = CallbackState.callbackStatePerThread.get();
Copy the code

By calling the method get(), you get an object associated only with the current thread.

Because many thread pools are used to process requests in application servers, java.lang.ThreadLocal can cause excessive memory consumption in this environment. Therefore, it is not recommended to use java.lang.ThreadLocal for classes that are executed by the request processing thread of the application server.

The messaging

If you don’t use the techniques above to share state, you need to use thread communication. That is, to pass messages between threads. You can use concurrent queues in the java.util.concurrent package for messaging. Or use a framework like Akka, which is an Actor-style concurrency framework. The following example shows how to send a message using Akka:

target.tell(message, getSelf());
Copy the code

Receiving messages:

@Override
public Receive createReceive(a) {
     return receiveBuilder()
        .match(String.class, s -> System.out.println(s.toLowerCase()))
        .build();
}
Copy the code

Immutable state

To avoid the problem of the sending thread changing the message while another thread reads it, the message should be immutable. Therefore, the Akka framework has a convention that all messages must be immutable

When implementing immutable classes, declare their fields final. This not only ensures that the compiler can check that the field is immutable, but that it can be correctly initialized even if an error occurs. Here is an example of a final instance variable:

public class ExampleFinalField
{
    private final int finalField;
    public ExampleFinalField(int value)
    {
        this.finalField = value; }}Copy the code

Use the data structures in java.util.concurrent

Messaging uses concurrent queues for communication between threads. Concurrent queues are one of the data structures provided in the java.util.Concurrent package. The java.util.concurrent package provides concurrent maps, queues, dequeues, sets, and lists. These data structures are highly optimized and thread-safe tested.

Synchronized field

If none of the above techniques are used, synchronization locks can also be used. By adding a lock at the synchronized block, you ensure that only one thread can execute this part at a time.

synchronized(lock)
{
    i++;
}
Copy the code

Note that there is a risk of deadlocks when using multiple nested synchronized blocks. Deadlocks occur when two threads try to acquire locks and objects held by the other thread.

Volatile field

Normally, non-volatile fields can be cached in registers or caches. By declaring the variable volatile, you tell the JVM and compiler to always return the most recent written value. This applies not only to the variable itself, but to all values written by the thread that writes the volatile field. Examples of volatile instance variables:

public class ExampleVolatileField
{
    private volatile int  volatileField;
}
Copy the code

More skills

  • Atomic update: A technique in which cpu-provided atomic instructions such as compare and set are invoked
  • Java. Util. Concurrent. The locks. Already: a lock implementation, provides more flexibility than synchronized blocks
  • Java. Util. Concurrent. The locks. ReentrantReadWriteLock: a lock, which read operation does not lock, read and write and write lock operation
  • Java. Util. Concurrent. The locks. StampedLock: a non-permanent read-write lock, read the values in the form of optimistic locking.

conclusion

The best way to achieve thread-safety is to avoid sharing state. If you need to share state, you can use messaging, immutable classes, concurrent data structures, synchronized fields, and volatile fields. If you want to test your application for thread safety, try VMLens for free.