Explain different ways of creating a thread
Threads can be used by either:
- Extending the Thread class.
- Implementing the Runnable interface.
Using the Executor framework
(this creates a thread pool)
By extends:
class Counter extends Thread {
//method where the thread execution will start
public void run(){
//logic to execute in a thread
}
//let’s see how to start the threads
public static void main(String[] args){
Thread t1 = new Counter();
Thread t2 = new Counter();
t1.start(); //start the first thread. This calls the run() method.
t2.start(); //this starts the 2nd thread. This calls the run() method.
}
}
By implements:
class Counter extends Base implements Runnable{
//method where the thread execution will start
public void run(){
//logic to execute in a thread
}
//let us see how to start the threads
public static void main(String[] args){
Thread t1 = new Thread(new Counter());
Thread t2 = new Thread(new Counter());
t1.start(); //start the first thread. This calls the run() method.
t2.start(); //this starts the 2nd thread. This calls the run() method.
}
}
The thread pool is more efficient and learn why and how to create pool of threads using the executor framework
.
What is a thread leak? What does it mean in Java
Thread leak is when a application does not release references to a thread object properly. Due to this some Threads do not get garbage collected and the number of unused threads grow with time. Thread leak can often cause serious issues on a Java application since over a period of time too many threads will be created but not released and may cause applications to respond slow or hang.
Can two threads call two different synchronized instance methods of an Object
No. If a object has synchronized instance methods then the Object itself is used a lock object for controlling the synchronization. Therefore all other instance methods need to wait until previous method call is completed. See the below sample code which demonstrate it very clearly. The Class Common has 2 methods called synchronizedMethod1() and synchronizedMethod2() MyThread class is calling both the methods
public class Common {
public synchronized void synchronizedMethod1() {
System.out.println(“synchronizedMethod1 called”);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“synchronizedMethod1 done”);
}
public synchronized void synchronizedMethod2() {
System.out.println(“synchronizedMethod2 called”);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“synchronizedMethod2 done”);
}
}
public class MyThread extends Thread {
private int id = 0;
private Common common;
public MyThread(String name, int no, Common object) {
super(name);
common = object;
id = no;
}
public void run() {
System.out.println(“Running Thread” + this.getName());
try {
if (id == 0) {
common.synchronizedMethod1();
} else {
common.synchronizedMethod2();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Common c = new Common();
MyThread t1 = new MyThread(“MyThread-1”, 0, c);
MyThread t2 = new MyThread(“MyThread-2”, 1, c);
t1.start();
t2.start();
}
}
Explain how you would get thread-safety issues due to non-atomic operations with a code example
The code snippets below demonstrates non-atomic operations producing incorrect results with code. The program below uses a shared Counter object, that is shared between three concurrent users (i.e. three threads). The Counter object is responsible for incrementing the counter.
Firstly, the Counter class. The counted values are stored in a HashMap by name (i.e. thread name) as the key for later retrieval.
import java.util.HashMap;
import java.util.Map;
public class Counter {
// shared variable or resource
private Integer count = Integer.valueOf(0);
private Map<String, Integer> userToNumber = new HashMap<String, Integer>(10);
public void increment() {
try {
count = count + 1; // increment the counter
Thread.sleep(50); // to imitate other operations and
// to make the racing condion to occur more often for the demo
Thread thread = Thread.currentThread();
userToNumber.put(thread.getName(), count);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
public Integer getCount(String name) {
return userToNumber.get(name);
}
}
Next, the Runnable task where each thread will be entering and executing concurrently.
public class CountingTask implements Runnable
{
private Counter counter;
public CountingTask(Counter counter) {
super();
this.counter = counter;
}
public void run() {
counter.increment();
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + ” value is ” + counter.getCount(thread.getName()));
}
}
Finally, the Manager class that creates 3 new threads from the main thread.
public class CountingManager {
public static void main(String[] args) throws InterruptedException
{
Counter counter = new Counter(); // create an instance of the Counter
CountingTask task = new CountingTask(counter); // pass the counter to the runnable CountingTask
//Create 10 user threads (non-daemon) from the main thread that share the counter object
Thread thread1 = new Thread(task, “User-1”);
Thread thread2 = new Thread(task, “User-2”);
Thread thread3 = new Thread(task, “User-3”);
//start the threads
thread1.start();
thread2.start();
thread3.start();
//observe the racing conditions in the output
}
}
To see the racing condition, inspect the output of the above code:
User-3 value is 3
User-1 value is 3
User-2 value is 3
All three threads or users get assigned the same value of 3 due to racing conditions. We are expecting to see three different count values to be assigned from 1 to 3. What happened here is that when the first thread incremented the count from 0 to 1 and entered into the sleep(50) block, the second and third threads incremented the counts from 1 to 2 and 2 to 3 respectively.
This shows that the 2 operations — the operation that increments the thread and the operation that stores the incremented value in a HashMap are not atomic, and produces incorrect results due to racing conditions.