Importance of Using ThreadLocal Variables in Parallel Execution Of Automation Scripts

In previous post, we see some common mistakes which we do while running selenium automation scripts in parallel. As we know now that object variables are shared by threads created by same object, we need a way to keep object variables unique to each threads so that we don’t get any data inconsistency when reading or writing same variables.

To achieve the same we can use ThreadLocal class of Java. Let me give you a basic idea about it and you will understand how class name itself gives you a hint about it.

If you want that a variable should be accessed by same thread by which it is created, we can use ThreadLocal variables. This class makes variables thread safe. Each thread of same object will have separate copy of object variables.

Official Javadoc defines ThreadLocal class as :-

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

ThreadLocal class provides below methods:-

  1. initialValue() :- Returns the current thread’s “initial value” for this thread-local variable
  2. set(T value) :- Sets the current thread’s copy of this thread-local variable to the specified value
  3. get() :- Returns the value in the current thread’s copy of this thread-local variable. If the variable has no value for the current thread, it is first initialized to the value returned by an invocation of the initialValue() method.
  4. remove() :- Removes the current thread’s value for this thread-local variable.

Just to summarize, A ThreadLocal variable could be initialized with an initial value by overriding initialValue() method. Use set() method to set the value for variable , get() to get it and remove() to remove it. There are plenty of examples given on ThreadLocal on internet. Refer JournalDev and Jenkov. These two posts are great.

Let’s rewrite the same RegisterUser class from previous post with a ThreadLocal variable.

Since class variable “resgiteredUserName” will be shared among threads so lets have it as a ThreadLocal variable.

package ThreadLocalUsageInSelenium;

import java.util.Random;

/*
 * This class has a method to register user. Implementing this class to run as a Thread.
 */
public class ThreadLocalRegisterUser implements Runnable{
	
	ThreadLocal threadLocal = new ThreadLocal(){
	    @Override protected String initialValue() {
	        return "No Value";
	    }
	};    
	String registeredUserName;

	@Override
	public void run() {
		createUser();
		try {
			getUserWithDelay();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void createUser()
	{
		System.out.println(Thread.currentThread().getName()+"  Before starting registration, value of registeredUserName :"+threadLocal.get());
		System.out.println(Thread.currentThread().getName()+" Registering a user.");
		registeredUserName = Thread.currentThread().getName()+" User"+ new Random().nextInt(999);
		threadLocal.set(registeredUserName);
		System.out.println(Thread.currentThread().getName()+" After registration, value of registeredUserName :"+threadLocal.get());	
	}
	
	private void getUserWithDelay() throws InterruptedException
	{
		Thread.sleep(5000);
		System.out.println(Thread.currentThread().getName()+" After some delay , value of registeredUserName :"+threadLocal.get());	
		
	}

}

Test Class :-

package ThreadLocalUsageInSelenium;

public class ResgisterUserTestThreadLocal {

 public static void main(String[] args) throws InterruptedException { 
 
 ThreadLocalRegisterUser registerUser = new ThreadLocalRegisterUser();
 // Creating two threads and both are using same instance of  ThreadLocalRegisterUser class
 Thread thread1 = new Thread(registerUser);
 Thread thread2 = new Thread(registerUser);
 // Starting first thread
 thread1.start();
 // Giving a pause
 Thread.sleep(2000);
 // Starting another thread
 thread2.start();
 
 }
}

Run above class and observe output.

Thread-0  Before starting registration, value of registeredUserName :No Value
Thread-0 Registering a user.
Thread-0 After registration, value of registeredUserName :Thread-0 User591
Thread-1  Before starting registration, value of registeredUserName :No Value
Thread-1 Registering a user.
Thread-1 After registration, value of registeredUserName :Thread-1 User546
Thread-0 After some delay , value of registeredUserName :Thread-0 User591
Thread-1 After some delay , value of registeredUserName :Thread-1 User546

Observe that value of registeredUserName is not reused by another thread as both threads have their own copy of object variable named “registeredUserName”.

Let’s rewrite Selenium example from previous post using ThreadLocal class.

When we run @Test annotated methods in parallel, each test methods will be run by a different thread. Both threads will be sharing WebDriver class variable. So let’s declare it as ThreadLocal.

WebDriverFactory class:-

package ThreadLocalUsageInSelenium;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

import io.github.bonigarcia.wdm.WebDriverManager;

public class WebDriverFactory {
	
	private WebDriver driver;
	
	public void setDriver() {
		WebDriverManager.chromedriver().setup();
		driver = new ChromeDriver();
	}
	
	public WebDriver getDriver()
	{
		return driver;
	}
	

}

Test Class:-

package ThreadLocalUsageInSelenium;

import org.openqa.selenium.WebDriver;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class WebDriverTest2 {
	
	// I am not using static ThreadLocal variable here bcz I have kept it in test class where it is being used.
	// If we keep it in another class, you can use it as static with static setter & getter methods.
	private ThreadLocal webdriver = new ThreadLocal();
	
	@BeforeMethod
	public void setUpBrowser()
	{
		WebDriverFactory webDriverFactory = new WebDriverFactory();
		webDriverFactory.setDriver();
		webdriver.set(webDriverFactory.getDriver());
	}

	@Test
	public void test1()
	{
		
		webdriver.get().get("https://www.google.com/");
		System.out.println("Title printed by "+Thread.currentThread().getName()+webdriver.get().getTitle());
		webdriver.get().close();
	}
	
	@Test
	public void test2()
	{
		webdriver.get().get("https://www.facebook.com/");
		System.out.println("Title printed by "+Thread.currentThread().getName()+webdriver.get().getTitle());
		webdriver.get().close();
	}
}

Run above tests parallel using testng xml as below:-




  
    
      
    
   
 

Output:-

 
 Starting ChromeDriver 80.0.3987.106 (f68069574609230cf9b635cd784cfb1bf81bb53a-refs/branch-heads/3987@{#882}) on port 8466
 Only local connections are allowed.
 Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code.
 Starting ChromeDriver 80.0.3987.106 (f68069574609230cf9b635cd784cfb1bf81bb53a-refs/branch-heads/3987@{#882}) on port 11901
 Only local connections are allowed.
 Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code.
Title printed by TestNG-test=Test-1Google
Title printed by TestNG-test=Test-2Facebook โ€“ log in or sign up
 Suite
Total tests run: 2, Passes: 2, Failures: 0, Skips: 0

Both threads have control on browser instance initiated by them now and there will be no session overriding by threads.

Note:- Since I am running test methods in parallel so I need to apply Thread Safe at TestNG class level. If you are running classes or tests in parallel, you need to identify shared resource and make them thread safe. We will see this example in next post.

You can download code form this repo.

If you have any doubt, feel free to comment below.
If you like my posts, please like, comment, share and subscribe.
#ThanksForReading
#HappyLearning

You can find all Selenium related post here.
You can find all API manual and automation related posts here.
You can find frequently asked Java Programs here.

6 thoughts on “Importance of Using ThreadLocal Variables in Parallel Execution Of Automation Scripts

  1. Hello Amod,
    Thanks for such detailed content . i was able to achieve parallel execution due to our post . Great work .All the Best ๐Ÿ™‚ Hearty Thanks

  2. Hello Amod, Thanks for this article.

    I am trying to implement parallel execution in my Junit 5 Automation framework (@Execution(ExecutionMode.concurrent)) but though it opens 2 browsers for my test cases but it executes only 1 test case in browser 1 and once it is complete then only it goes to the other browser for testcase 2. What can be the issue here ?
    Test Cases flow :
    1. BrowserDriver .java class has the ThreadLocal webdriver variable and browser launch methods and also the getDriver method.
    2. Test cases are in a separate class which extends the BaseTestCase.java class.
    3. @BeforeEach / @AfterEach, @BeforeAll/@AfterAll – are all present in BaseTestCase .java
    4. Browser initiation , Data Load all happens in @BeforeEach Method.

    Can you pls help me why am i seeing this issue ?

    1. Hi,
      Only using ThreadLocal doesnโ€™t solve all issues in parallel execution. If you are running all test methods in parallel, make sure all initialisation happen for each method call.

  3. Hello Amod,

    Thanks for a wonderful article. After referring this article, I tried to implement the same in my sample project. I was unable to run it successfully. The actions are getting clubbed on single browser and thus unstable. Is this approach feasible in Page Object Model ?

    Thanks,
    Deven J.

Leave a Reply

Your email address will not be published. Required fields are marked *