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.
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.
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.
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.
//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.
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:
//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
private static int i=0;
public void syncMethod() {
i++;
System.out.println("syncTestBroken" + i);
}
Usage:
syncTest st = new syncTest();
st.syncMethod();
3 threads called this method and below is the output
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.
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:
syncTestFixed stf = new syncTestFixed();
stf.syncMethod();
Output:
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.
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:
syncTestFixed stf = new syncTestFixed();
stf.syncBlock();
Output:
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
- The class you wish to be represented as the ThreadLocal object (your driver class)
- A manager class, the class which sets and gets the instance of the object
- 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)
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()
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)
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
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.
DriverManager.getDriver().rwd