Thursday, May 21, 2015

Synchronizing Java Method In A Cluster

In a multi-threaded environment, sometimes we have to write a method with a requirement that, at any point of time only one thread can execute the method. And we can achieve this easily with the java synchronized keyword. Declaring a java method as synchronized we can ensure that at any point of time only one thread in the JVM can execute the method. So its very simple to make a method synchronized in a JVM.

@Component
class MyClass{
  public synchronized void doSomething(){
      // do something
  }
}

The above strategy will work fine when your method is on a single JVM. What will happen if your method is executable on multiple JVMs ?  For example your method is a part of application deployed into multiple servers in your environment. So the above strategy of synchronizing your method will not work in a clustered environment. Because synchronized keyword can ensure synchronization only on single JVM. There are different ways to solve the problem, one of them is by using Hazelcast. In this example I am using maven, spring and Hazelcast to demonstrate the solution.


Step:1 Add Hazelcast Dependency


Add Hazelcast dependency to your pom or manually add the jar if it not a maven project.

<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast</artifactId>
    <version>3.2</version>
</dependency>


Step:2 Create a Hazelcast Instance During Application Startup


Create a Hazelcast object during application startup. Here I am using spring, so I will create Hazelcast object using spring bean , which will be instantiated when the spring context will be loaded. You can create the java object manually if you are not using spring.

<bean id="hazelcast" 
      class="com.hazelcast.core.Hazelcast" 
      factory-method="newHazelcastInstance"
   <constructor-arg>
      <bean class="com.hazelcast.config.Config">
        <property name="groupConfig">
          <bean class="com.hazelcast.config.GroupConfig">
            <property name="name" value="dev-cluster"/>
            <property name="password" value="secret"/>
          </bean>
        </property>
        <property name="networkConfig">
          <bean class="com.hazelcast.config.NetworkConfig">
            <property name="port" value="5555"/>
            <property name="join">
              <bean class="com.hazelcast.config.JoinConfig">
                <property name="tcpIpConfig">
                  <bean class="com.hazelcast.config.TcpIpConfig">
                    <property name="enabled" value="true"/>
                    <property name="members" value="hostname1,hostname2"/>
                  </bean>
                </property>
                <property name="multicastConfig">
                  <bean class="com.hazelcast.config.MulticastConfig">
                    <property name="enabled" value="false"/>
                  </bean>
                </property>
              </bean>
            </property>
          </bean>
        </property>
      </bean>
   </constructor-arg>
</bean>

Important Configuration Values :

dev-cluster : 
It is to give a name to the cluster.
secret : 
It is to give a password for the cluster members.
5555 : 
Port on which cluster members can talk to each other
hostname1, hostname2 : 
IP Address or Host Name of the members of the cluster separated by comma

Step:3 Hazelcast Object Should Be Accessible From Your Component


Create a new property in your class to hold this Hazelcast instance object and set it's value when you instantiate that class. Since I am using spring, I will set it in my class by autowiring. 

@Component
class MyClass{
  @Autowired
  Hazelcast hazelcast;
  public synchronized void doSomething(){
      // do something
  }
}

Step:4 Use Hazelcast Instance In Your Method To Make It Synchronized


Now I will change my method a little bit using Hazelcast to make it synchronized in a cluster. Now no need to use the synchronized keyword, so I am removing it from method signature. Add wrap your existing code as below. 

@Component
class MyClass{ 
  @Autowired
  Hazelcast hazelcast;
  public void doSomething(){
      ILock lock = hazelcast.getLock("mykey");
      try{
         lock.lock();
         // do something
      } catch (Exception e ) {
         // handle it in your own way
      } finally(){
         lock.unlock();
      }
  }
}

ILock lock = hazelcast.getLock("mykey");
This is to get the cluster level lock object with the name "mykey" and if no lock with such name exists in the cluster then it will first create and then return the lock. One lock can be created with a name.

lock.lock();
This is to acquire the cluster level lock. If a lock is acquired by any thread in the cluster, then no other thread can't acquire it, all others will go to waiting state. In a thread it can be acquires  only when it is not acquired. So at any point of time only one thread in the cluster can acquire the lock. 

NB :
To ensure threads will not go to wiating state for ever, you can pass the timeout value as parameter to the method, so that if a thread can not acquire the lock within that time period, this method can throw exception.

lock.unlock();
This is to release the lock acquired before, so that other threads in the cluster can acquire it.

So you are done.  This is how, you can very easily synchronize a java method in a cluster. Hazelcast can be used for more than this, it is just a very simple use of Hazelcast.

Happy Clustering !!!

3 comments: