SynchronizationS2C 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 MethodsTop

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.

sync1 sync2 sync3 sync4 sync5 sync5

Press the button below to cycle through our Synchronized Methods explanation.


Using the synchronized KeywordTop

Now we will write some code to illustrate the above slideshow in action:


/*
  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.

test sync

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 ExampleTop

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:


/*
  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.

test account

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.

test account2

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 BlocksTop

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:


/*
  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.

test credit

Well we can clearly see from the output that we are losing half the credits to the account.

Using A Synchronized BlockTop

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:


/*
  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.

test credit2

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 ScenarioTop

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:


/*
  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.

test deadlock

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.

go to home page Homepage go to top of page Top