SynchronizationJ8 Home « Synchronization
Running separate threads of execution at the same time is great and we can certainly improve the performance of our programs using multithreading. But what happens when concurrent threads need to modify the contents of a file or some state within an instance? We could end up with corrupt data, lost updates, unstable object state and all sorts of other unpleasantness when multiple threads are all trying to update the same resource at the same time. What we really need is a way to control access to sensitive data so that only one thread has access to the resource at a time. Java deals with this problem using synchronization and in this lesson we investigate how we can control access to our code by synchronizing at the method or block level.
in Java every class has a lock and each instance of a class we instantiate also has it's own lock. Most of the time we write code these locks are transparent and until reading this sentence you may have been totally
unaware about Javas locking system. Its only when we have to deal with situations where a resource can be updated concurrently that we have be aware of locking. So how does locking work? Well locking only comes into
play when we synchronize our code using the synchronized
keyword. As mentioned above we can lock methods or blocks of code this way and when we do this we lock this section of code from being executed
by other threads for the duration of the section of code.
The main emphasis of the lesson will be about synchronization of objects, but before we get into this we will talk briefly about static
initializer blocks and static
methods. We don't need
to worry about static
initializer blocks as these are inherently thread safe, as they only ever get called once per class-loader. When we apply synchronization to the static
methods of
a class, only one of the synchronized static
methods can execute at any point in time. This type of locking is done at the class level and a class lock is totally independent of any object locks, in
much the same way as static
members are independent of any instance. The way we synchronize static
methods is no different from the way we synchronize non-static methods; just
remember they use different locking mechanisms and apply to a class and an instance of a class respectively.
Synchronized Methods Top
At the method level synchronization is achieved by checking the lock that every instance has associated with it. When a thread tries to enter a synchronized method, it first checks to see if the lock for this object is available, or put another way, unlocked. If it is then the thread is allowed entry to the method and the lock for this object is locked. This applies to all synchronized methods within the class, so once a lock has been set for an object all other synchronized methods are locked out for that object. When the object returns from the method that the lock was set on, the object becomes unlocked, so any threads waiting for this object's lock have a chance to enter a synchronized method and restart the locking/unlocking process. In this way we can guarantee that we only ever enter one synchronized method for an object at a time.
A really important thing to remember here is that we are only excluding other threads of the same object from entering the synchronized code. If we had three threads, representing three different objects, then they all have different locks and therefore could all use the same synchronized method concurrently. Also we don't need to worry about local variables as each thread gets its own set of these on entry to a method. Two threads running the same method concurrently will have different copies of the local variables that will work independently.
This is a lot of information to get our heads around so before we look at concurrency issues, we will illustrate some synchronization usage with a slide show; just press the button below to step through each slide.
Using the synchronized
Keyword Top
Now we will write some code to illustrate the above slideshow in action:
package info.java8;
/*
Running threads through some synchronized methods
*/
public class SyncTest implements Runnable {
// Override run() method of Runnable interface to execute a new thread
public void run() {
// Get current active thread
Thread activeThread = Thread.currentThread();
System.out.println("The thread named: " + activeThread.getName() + " is starting.");
method1();
method2();
System.out.println("The thread named: " + activeThread.getName() + " is ending.");
}
// Sync method 1
synchronized public void method1() {
// Get current active thread
Thread activeThread = Thread.currentThread();
System.out.println("The thread named: " + activeThread.getName() + " is entering method1().");
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
System.out.println(ex);
}
System.out.println("The thread named: " + activeThread.getName() + " is exiting method1().");
}
// Sync method 2
synchronized public void method2() {
// Get current active thread
Thread activeThread = Thread.currentThread();
System.out.println("The thread named: " + activeThread.getName() + " is entering method2().");
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
System.out.println(ex);
}
System.out.println("The thread named: " + activeThread.getName() + " is exiting method2().");
}
public static void main(String[] args) {
System.out.println("Main user thread has started.");
// Create some Runnable objects
SyncTest st1 = new SyncTest();
SyncTest st2 = new SyncTest();
// Create three threads
Thread th1 = new Thread(st1, "th1");
Thread th2 = new Thread(st1, "th2");
Thread th3 = new Thread(st2, "th3");
// Power up the threads via the start() method
th1.start();
th2.start();
th3.start();
System.out.println("Main user thread has ended.");
}
}
Save, compile and run the SyncTest
class in directory c:\_Concurrency in the usual way.
The first thing we do in the SyncTest
class is implement the Runnable
interface. We then create two runnable objects of type SyncTest
with references of 'st1' and 'st2'.
We then use the 'st1' SyncTest
object in the construction of two new threads 'th1' and 'th2'. We then use the 'st2' SyncTest
object in the construction of another threads 'th3'. We then
start all three threads. Our override of the run()
method, calls two synchronized
methods, namely method1()
and method2()
. Either 'th1' or 'th2' as well
as 'th3' will enter the synchronized
method method1()
. If 'th1' enters the method1()
method then the lock for 'st1' is locked on this thread and 'th2' is locked out. If 'th2'
enters the method1()
method then the lock for 'st1' is locked on this thread and 'th1' is locked out. The thread named 'th3' will run concurrently with either 'th1' or 'th2' as this thread is a different
object and therefore has a different lock. When either 'th1' or 'th2' returns from the method1()
method then the lock for 'st1' becomes unlocked and either 'th1' or 'th2' gets the chance to run again.
Eventually all the threads end and the program ends. Hopefully you can see from the output the locking that occurs between 'th1' and 'th2'.
A Working Example Top
In this example we will show what can go wrong when code has concurrent access to an object and how making a method synchronized can fix the problem:
package info.java8;
/*
A simple bank account scenario
*/
public class TestBankAccount implements Runnable {
private int balance;
// TestBankAccount constructor
public TestBankAccount (int balance) {
// Set balance
this.balance = balance;
}
// Override run() method of Runnable to execute a new thread
public void run() {
// Get current active thread
Thread activeThread = Thread.currentThread();
System.out.println("The thread named: " + activeThread.getName() + " is starting.");
debitAccount(30);
debitAccount(30);
debitAccount(10);
System.out.println("The thread named: " + activeThread.getName() + " is ending.");
}
// Debit account if balance zero or greater after debit
public void debitAccount (int debit) {
Thread activeThread = Thread.currentThread();
System.out.println("Thread named: " + activeThread.getName() + " entering debitAccount ().");
// Check to see if we can debit amount
if (balance >= debit) {
try {
Thread.sleep(2000);
System.out.println("Debiting thread named: " + activeThread.getName() + " by " + debit);
balance -= debit;
} catch (InterruptedException ex) {
System.out.println(ex);
}
} else {
System.out.println("Cannot debit thread named: " + activeThread.getName() + " by " + debit
+ ". Not enough money in account!");
}
System.out.println("Account balance is now: " + balance);
System.out.println("Thread named: " + activeThread.getName() + " exiting debitAccount ().");
}
public static void main(String[] args) {
System.out.println("Main user thread has started.");
// Create a Runnable object
TestBankAccount tba = new TestBankAccount(100);
// Create two threads
Thread th1 = new Thread(tba, "jack");
Thread th2 = new Thread(tba, "jill");
// Power up the threads via the start() method
th1.start();
th2.start();
System.out.println("Main user thread has ended.");
}
}
Save, compile and run the TestBankAccount
class in directory c:\_Concurrency in the usual way.
The first thing we do in the TestBankAccount
class is implement the Runnable
interface. We then create a runnable objects of type TestBankAccount
with a references of
'tba'. We then use the 'tba' SyncTest
object in the construction of two new threads 'th1' and 'th2'. We then start our two threads. Our override of the run()
method, calls the
debitAccount
method with a debit amount. We check the account balance can be modified without the total going under 0 and then sleep the thread for 2 seconds. When the thread wakes up we then
debit the amolunt from the account. As you can see from the screenshot even though we check the balance, because two threads are running concurrently in the debitAccount
method we actually end up with
a negative balance which is not what we want.
All we need to do to make the debitAccount
method thread safe is to add the keyword synchronized
to the start of this method. So do this and then save, compile and run the
TestBankAccount
class in directory c:\_Concurrency in the usual way.
As you can see from the screenshot now only one thread can enter the the synchronized debitAccount
method and the program now works correctly. A point to remember is that sleeping a thread
doesn't release an object's lock, the lock is only released when that method ends.
Synchronized Blocks Top
Using synchroniaztion does come at a cost, when we synchronize a method there is extra processing that needs to be done every time the method is entered to check that the lock is available. There is also the fact that while one thread has the lock, any other threads for this object that need to use the synchronized method are locked out and queued, until the active thread releases the lock. Java also allows us to synchronize a block of code instead of a whole method, which can make the lock time less of an overhead and therefore our synchronized code more efficient. Lets see an example of some code with concurrency issues and how we can synchronize a block of code to correct the problem:
package info.java8;
/*
A simple bank credit scenario
*/
public class TestBankCredit implements Runnable {
private int balance;
// TestBankCredit constructor
public TestBankCredit (int balance) {
// Set balance
this.balance = balance;
}
// Override run() method of Runnable to execute a new thread
public void run() {
// Get current active thread
Thread activeThread = Thread.currentThread();
System.out.println("The thread named: " + activeThread.getName() + " is starting.");
for (int i=0; i<3; i++) {
creditAccount(10);
}
System.out.println("The thread named: " + activeThread.getName() + " is ending.");
}
// Credit account
public void creditAccount (int credit) {
Thread activeThread = Thread.currentThread();
System.out.println("Thread named: " + activeThread.getName() + " entering creditAccount().");
// Move balance to int and then update from int so its non-atomic
int i = balance;
System.out.println("Thread named: " + activeThread.getName() + " about to add "
+ credit + " pounds.");
balance = i + credit;
System.out.println("Account balance is now: " + balance);
System.out.println("Thread named: " + activeThread.getName() + " exiting creditAccount().");
}
public static void main(String[] args) {
System.out.println("Main user thread has started.");
// Create a Runnable object
TestBankCredit tbc = new TestBankCredit(0);
// Create two threads
Thread th1 = new Thread(tbc, "credit-1");
Thread th2 = new Thread(tbc, "credit-2");
// Power up the threads via the start() method
th1.start();
th2.start();
System.out.println("Main user thread has ended.");
}
}
Save, compile and run the TestBankCredit
class in directory c:\_Concurrency in the usual way.
Well we can clearly see from the output that we are losing half the credits to the account.
Using A Synchronized Block Top
We know we could synchronize on the method to correct this, but lets synchronize just a part of the method so the code is locked for less time:
package info.java8;
/*
A simple bank credit scenario (take 2)
*/
public class TestBankCredit implements Runnable {
private int balance;
// TestBankCredit constructor
public TestBankCredit (int balance) {
// Set balance
this.balance = balance;
}
// Override run() method of Runnable to execute a new thread
public void run() {
// Get current active thread
Thread activeThread = Thread.currentThread();
System.out.println("The thread named: " + activeThread.getName() + " is starting.");
for (int i=0; i<3; i++) {
creditAccount(10);
}
System.out.println("The thread named: " + activeThread.getName() + " is ending.");
}
// Credit account
public void creditAccount (int credit) {
Thread activeThread = Thread.currentThread();
System.out.println("Thread named: " + activeThread.getName() + " entering creditAccount().");
// Here is where out problems with concurrency are so we can synchronize the following code
synchronized (this) {
// Move balance to int and then update from int so its non-atomic
int i = balance;
System.out.println("Thread named: " + activeThread.getName() + " about to add "
+ credit + " pounds.");
balance = i + credit;
}
System.out.println("Account balance is now: " + balance);
System.out.println("Thread named: " + activeThread.getName() + " exiting creditAccount().");
}
public static void main(String[] args) {
System.out.println("Main user thread has started.");
// Create a Runnable object
TestBankCredit tbc = new TestBankCredit(0);
// Create two threads
Thread th1 = new Thread(tbc, "credit-1");
Thread th2 = new Thread(tbc, "credit-2");
// Power up the threads via the start() method
th1.start();
th2.start();
System.out.println("Main user thread has ended.");
}
}
Save, compile and run the TestBankCredit
class in directory c:\_Concurrency in the usual way.
As you can see from the screenshot now only one thread can enter the synchronized code block at a time and the program now works correctly. What we are doing here is just synchronizing on the current object and doing the updates to our object within the synchronized block.
Deadlock Scenario Top
There is a situation that can arise when using synchronized code that we as programmers need to be aware, which is known as deadlocking. A deadlock can occur when an object is locked and the thread of execution tries to access another object. The other object is also locked and is trying to access this thread of executions locked object. The object locks are basically locking each other out forever and thus are deadlocked. Lets see an example of this in action with some code:
package info.java8;
/*
A deadlock example
*/
public class TestDeadlock {
static class DeadlyEmbrace {
private final String name;
public DeadlyEmbrace(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public synchronized void method1(DeadlyEmbrace de) {
// Get current active thread
Thread activeThread = Thread.currentThread();
System.out.println("Thread named: " + activeThread.getName() + " is entering method1().");
System.out.format("%s:" + " synched in method1()."
+ " passed parameter. " + "%s: %n", this.name, de.getName());
de.method2(this); // Deadlock
}
public synchronized void method2(DeadlyEmbrace de) {
System.out.format("%s:" + " synched in method2()."
+ " passed parameter. " + "%s: %n", this.name, de.getName());
}
}
public static void main(String[] args) {
final DeadlyEmbrace de1 = new DeadlyEmbrace("jack");
final DeadlyEmbrace de2 = new DeadlyEmbrace("jill");
// Create runnable thread using anonymous inner class
new Thread(new Runnable() {
public void run() { de1.method1(de2); }
}).start();
// Create runnable thread using anonymous inner class
new Thread(new Runnable() {
public void run() { de2.method1(de1); }
}).start();
}
}
Save, compile and run the TestDeadlock
class in directory c:\_Concurrency in the usual way.
As you can see from the screenshot there is a strong possibility of deadlock when either of the threads tries to enter the second synchronized method named method2()
. Each object will in all
likelihood sit there forever waiting for the other to exit the first synchronized method named method1()
and release the lock the other requires to enter the other synchronized method.
Lesson 3 Complete
In this lesson we looked at synchronization and how we can control access to our code at the method or block level.
What's Next?
In the next lesson we investigate thread priorities and how we can have the priority set automatically by the invoking thread or assign thread priorities ourselves using methods of the Thread
class.