Java Synchronization Best Practices


1. Overview

In this post, I would like to explain the best practices for Java Synchronization. This post belongs to my favorite Java Best Practices Series category.

Avoid or Minimize Synchronization - Improper synchronization can also cause a deadlock, which can result in complete loss of service because the system usually has to be shut down and restarted. But performance overhead cost is not a sufficient reason to avoid synchronization completely. Failing to make sure your application is thread-safe in a multithreaded environment can cause data corruption, which can be much worse than losing performance. 

2. Java Synchronization Best Practices

Let's discuss some practices that you can consider to minimize the overhead:
 Let's discuss each Synchronization best practices with an example.

2.1 Synchronize Critical Sections Only

If only certain operations in the method must be synchronized, use a synchronized block with a mutex instead of synchronizing the entire method. For example:
private Object mutex = new Object();
    ...
    private void doSomething()
    {
     // perform tasks that do not require synchronicity
        ...
        synchronized (mutex)
            {
            ...
            }
        ...
    }

2.2 Do Not Use the Same Lock on Objects That Are Not Manipulated Together

Every Java object has a single lock associated with it. If unrelated operations within the class are forced to share the same lock, then they have to wait for the lock and must be executed one at a time. In this case, define a different mutex for each unrelated operation that requires synchronization.
Also, do not use the same lock to restrict access to objects that will never be shared by multiple threads. For example, using Hashtables to store objects that will never be accessed concurrently causes unnecessary synchronization overhead:
public class myClass
{
    private static myObject1 myObj1;
    private static mutex1 = new Object();
    private static myObject2 myObj2;
    private static mutex2 = new Object();
    ...
    public static void updateObject1()
        {
            synchronized(mutex1)
            {
            // update myObj1 ...
            }
        }
    public static void updateObject2()
    {
      synchronized(mutex2)
        {
        // update myObj2 ...
        }
    }
...
}

2.3 Use Private Fields

Making fields private protects them from unsynchronized access. Controlling their access means these fields need to be synchronized only in the class's critical sections when they are being modified.

2.4 Use a Thread Safe Wrapper

Provide a thread-safe wrapper on objects that are not thread-safe.

2.5 Use Immutable Objects

An immutable object is one whose state cannot be changed once it is created. Since there is no method that can change the state of any of the object's instance variables once the object is created, there is no need to synchronize on any of the object's methods.
This approach works well for objects, which are small and contain simple data types. The disadvantage is that whenever you need a modified object, a new object has to be created. This may result in creating a lot of small and short-lived objects that have to be garbage collected. One alternative when using an immutable object is to also create a mutable wrapper similar to the thread-safe wrapper.
An example is a String and StringBuffer class in Java. The String class is immutable while its companion class StringBuffer is not. This is part of the reason why many Java performance books recommend using StringBuffer instead of string concatenation.

2.6 Know Which Java Objects Already Have Synchronization Built-in

Some Java objects (such as Hashtable, Vector, and StringBuffer) already have synchronization built into many of their APIs. They may not require additional synchronization.

2.7 Do Not Under-Synchronize

Some Java variables and operations are not atomic. If these variables or operations can be used by multiple threads, you must use synchronization to prevent data corruption.
For example,
(i) Java types long and double are comprised of eight bytes; any access to these fields must be synchronized. 
(ii) Operations such as ++ and -- must be synchronized because they represent a read and a write, not an atomic operation.

2.8 Monitor Synchronization

Java Synchronization can cause a deadlock. The best way to avoid this problem is to avoid the use of Java synchronization. One of the most common uses of synchronization is to implement pooling of serially reusable objects. Often, you can simply add a serially reusable object to an existing pooled object. For example, you can add Java Database Connectivity (JDBC) and Statement object to the instance variables of a single thread model servlet, or you can use the Oracle JDBC connection pool rather than implement your own synchronized pool of connections and statements.
If you must use synchronization, you should either avoid deadlock or detect it and break it. Both strategies require code changes. So, neither can be completely effective because some system code uses synchronization and cannot be changed by the application.
To prevent deadlock, simply number the objects that you must lock, and ensure that clients lock objects in the same order.

3. Conclusion

This post explained the best practices of Java Synchronization. Read more Java/J2EE best practices on Java Best Practices Series. Feel free to comment on this post and give us a suggestion or feedback.

4. Related Posts

Comments