Parallel execution | Thread local, safeness, and synchronization

When running parallel execution, it is extremely important to ensure the entirety of your framework is thread safe.

Impact of not being thread safe

When running on more than one thread your code may come crashing down as soon as it starts to execute when maybe it worked perfectly on one thread. Alternatively, if most of your framework is thread safe but only portions are not then you may think things are fine and then start spotting intermittent issues which could prove to be difficult to track down.

Static methods and objects

First it is important that when using static methods that all objects utilized within the method are local to the method. 

Proper usage of static methods will result in "test + <yourstring>" on every thread being returned.

Copy
public static String doString(String a) {
        String finalString="test";
        finalString = finalString+a;
        return finalString;        
    }

Improper usage of static methods will result in "test + <everyOtherStringYouEverPassToThisMethod> + <yourString>" being returned.

Copy
public static String finalString="test";
    
    public static String doString(String a) {
        finalString = finalString+a;
        return finalString;        
    }

When you use objects that exist at the class level in your static methods, you run into thread conflicts. Another example can be seen in the following example, in which someone has created a class to get and set the deviceID. 

Copy
class staticDemo{
    public static String deviceID;

    public static void setDeviceID(String deviceid) {
        deviceID=deviceid;
    }

    public static String getDeviceID() {
        return deviceID;
    }
}

The usage of this code is simple since we can directly call the methods from the class name.

Copy
        //sets device id
        staticDemo.setDeviceID(deviceid);
        //retrieves device id
        System.out.println(staticDemo.getDeviceID());

In a multi threaded environment though what do you think happens when thread 1 sets the device id, then thread 2 sets the device id, and thread 1 later asks for the device id? If you guessed thread 1 would receive the device id it fed to the set method you would be wrong. 

Console Output:

DeviceID2

Use constructors

To resolve these types of conflicts generally we use constructors in java and pass around objects which we want to exist on a per thread basis.

Rewritten deviceID code using a constructor will correct the issue and provide thread 1 it the correct device id even if the device id has most recently been set by thread 2.

Copy
public String deviceID;    
    public staticDemoFixed (String deviceid) {
        setDeviceID(deviceid);
    }

    public void setDeviceID(String deviceid) {
        this.deviceID = deviceid;
    }

    public String getDeviceID() {
        return deviceID;
    }

Code Usage is a bit more complex because we must initialize the new class and use the variable name to access the methods:

Copy
        //sets device id
        staticDemoFixed sdf = new staticDemoFixed(deviceid);
        //retrieves device id
        System.out.println(sdf.getDeviceID());

Now each thread will receive the value it stored for deviceID - Console Output:

DeviceID1

Synchronization

Another utility Java provides for handling threads is the synchronize command. This command provides you the ability to tell Java to only allow one thread at a time access to an object (block of code) or an entire method. Below I will provide examples of how to use synchronization and explain a use case where this would be beneficial with implementing remote web driver and Perfecto.

Note that synchronizing portions of your code will slow down your execution however sometimes, as you'll see below, it is necessary to perform.

Synchronized methods

Broken method

Here you will find a broken process in which synchronization will correct

Copy
private static int i=0;
    public void syncMethod() {        
        i++;
        System.out.println("syncTestBroken" + i);
    }

Usage:

Copy
syncTest st = new syncTest();
        st.syncMethod();

3 threads called this method and below is the output

Copy
syncTestBroken2
syncTestBroken2
syncTestBroken3

As you can see above we expected to receive 1, 2, 3 in order however we received 2, 2, 3. This is due to a race condition between the threads on accessing and printing the integer to the console.

Correct using a synchronized method

In order to synchronize a method simply add synchronized before the return type and after the access modifier for the method you want to sync.

Copy
private static int i=0;
//all code in below method is locked to a single thread at a time
public synchronized void syncMethod() {        
        i++;
        System.out.println("syncTestMethodFixed" + i);
    }

Usage:

Copy
syncTestFixed stf = new syncTestFixed();
        stf.syncMethod();

Output:

Copy
syncTestMethodFixed1
syncTestMethodFixed2
syncTestMethodFixed3

Correct using a synchronized block

In order to synchronize a block of code you must first create a static object which is accessible by all threads. Within the code you then place the synchronization on the static object and the code within the block will then be locked to a single thread instead of the entire method.

Copy
private static int a=0;
private final static Object objectLock= new Object();
public void syncBlock() {
        //all code in the below block is locked to a single thread at a time
        synchronized(objectLock) {
            a++;
            System.out.println("syncTestBlockFixed" + a);
        }        
        //do more here on ALL threads
    }

Usage:

Copy
syncTestFixed stf = new syncTestFixed();
        stf.syncBlock();

Output:

Copy
syncTestBlockFixed1
syncTestBlockFixed2
syncTestBlockFixed3

Real world use case

When grabbing devices from the Perfecto cloud you will usually want to perform this operation by defining attributes you wish the device to have using the capabilities commands; see Define capabilities. When running in a multi thread environment what you will find what happens quite often is when you request for the same device type from the cloud on different threads the same device id will be returned to each thread. When this occurs one of the threads will initialize and open the device and when the other threads try they will receive an an error from the cloud. Synchronizing your process for retrieving and opening a device from the cloud will resolve this issue.

ThreadLocal

The last topic to discuss is the usage of the ThreadLocal object type. When defining an object as ThreadLocal you are essentially telling the object to create a new instance for each thread which accesses it. All object exposed and used under this object should be local and exclusive to the thread using the object.

This would be idea for usage when creating the driver and all libraries associated with the instance of the driver. Below I will show you the usage pattern for the implementation.

You will require at minimum 3 class files

  1. The class you wish to be represented as the ThreadLocal object (your driver class)
  2. A manager class, the class which sets and gets the instance of the object
  3. Factory class, the class which holds the initial instance of the object setup

Driver Class (for purposes of this demo this empty but normally where you would initialize your driver, etc)

Copy
public class DriverClass {
    Public RemoteWebDriver rwd;
}

Manager Class (the class you want to be represented goes in between < and > in the thread local initialization. You want to return this class from getDriver() and set this class in setDriver()

Copy
public class DriverManager {
    private static ThreadLocal<DriverClass> driver = new ThreadLocal<DriverClass>();

    public static DriverClass getDriver() {
        return driver.get();
    }

    public static void setDriver(DriverClass drive) {
        driver.set(drive);
    }
} 

Factory Class (you want to have a return type of your class your using in thread local here for createInstance() and within the method initialize the instance of your class and return it)

Copy
public class DriverFactory {
    public static DriverClass createInstance() {
        DriverClass driver = new DriverClass();
        return driver;
    }
}

Usage

Usage after creating the various classes now is fairly simple.

First to initialize the object you would perform the following

Copy
DriverClass driver = DriverFactory.createInstance();
        DriverManager.setDriver(driver);

Lastly when you want to use the object you call it statically. This object is now available as a static reference to your entire thread within any class and each instance is local to the thread.

Copy
DriverManager.getDriver().rwd