Concurrent Programming In Java – PART FOUR

Home /

Table of Contents

How To Put A Thread To Sleep?

- Concurrent Programming In Java - PART FOUR

One of the best ways to help your threads take turns is to put them to sleep periodically. Simply call the static sleep() method and pass it the sleep duration in milliseconds.

For example:

Thread.sleep(1000);

will remove a thread from the running state and keep it from being runnable for one second.  The thread will not be able to become the running thread again until at least one second has passed.

Unfortunately, the sleep method throws an InterruptedException, which is a checked exception. So all sleep calls must be wrapped in a try/catch (or declared). So, a sleep call looks like this:

try {
    Thread.sleep(1000);
} catch(InterruptedException ex) {
    ex.printStackTrace();
}

Your thread will almost certainly never be interrupted from sleep. The exception is in the API to support a thread communication mechanism that almost no one uses in practice. However, you must still follow the handle or declare law. So you must become accustomed to wrapping your sleep() calls in a try/catch.

Now that you know your thread will not wake up before the specified time, is it possible that it will wake up after the ‘timer’ has expired? Both yes and no. It really doesn’t matter because when the thread wakes up, it always returns to the runnable state! The thread will not automatically wake up and become the currently-running thread at the specified time. When a thread wakes up, it is at the mercy of the thread scheduler once more. Now, for applications that don’t require perfect timing and only have a few threads, it may appear that the thread wakes up and resumes running on time (say, after 1000 milliseconds). But don’t bet your program on it.

Using Sleep To Make Our Program More Predictable

Remember our previous example, which gave us different results each time we ran it? Examine the code and the sample output again. Sometimes main had to wait until the new thread finished (and printed “This method is at the top of the stack!!”), but other times the new thread was sent back to runnable before it finished, allowing the main thread to come back in and print “Back In Main!”.  What can we do to change this? Stop for a moment and respond to the following question: “Where can you put a sleep() call to ensure that “Back In Main!” always prints before “This method is at the top of the stack!!”?

We’ll wait while you figure out an answer (there are several options).

Can you figure it out?

class RunnableTask implements Runnable{
 
    public void run() {
 
        doSomething();
       
    }
 
    public void doSomething() {
 
        try {
            Thread.sleep(1000);//Calling sleep here will force the new thread to exit its currently-running state!
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
       
        // The main thread will revert to the currently running thread and print "Back In Main." Then there will be a
        // one-second pause before we get to this line,
        // which calls doSomethingMore() and prints out "This method is at the top of the stack!!".
 
        doSomethingMore();
 
    }
 
    public void doSomethingMore() {
        System.out.println("This method is at the top of the stack!!");
    }
 
}
 
public class RunnableDemo3 {
 
    public static void main(String[] args) {
       
        Runnable runnableTask = new RunnableTask();
        Thread thread1 = new Thread(runnableTask);
 
        thread1.start();
 
        System.out.println("Back In Main!");
 
    }
   
}

Run this program and examine the output for yourself.

How To Make And Start Two Threads?

Threads have names. You can name your threads whatever you want, or you can accept their default names. However, the cool thing about names is that they can be used to determine which thread is currently running. The example below starts with two threads. Each thread performs the same function: it runs in a loop.  With each iteration, the name of the currently running thread is printed.

class RunThreadDemo implements Runnable {
 
    public static void main(String[] args) {
 
        RunThreadDemo runner = new RunThreadDemo();//Make one Runnable instance.
 
	  //Make two threads, with the same Runnable (the
        same job--we’ll talk more about the “two threads
        and one Runnable” in a few pages).
 
        Thread threadOne = new Thread(runner);
        Thread threadTwo = new Thread(runner);
 
	  //Name the threads.
        threadOne.setName("Thread #1");
        threadTwo.setName("Thread #2");
       
	  //Start the threads.	
        threadOne.start();
        threadTwo.start();
 
    }
 
    public void run() {
        // Each thread will run through this loop,
  //  printing its name each time.
 
        for (int i = 0; i < 25; i++) {
 
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " is running");
 
        }
    }
 
}

What will happen?

Will the threads take turns?  Will you notice that the thread names alternate? How frequently will they switch? With each subsequent iteration? After five iterations? You already know the answer: we have no idea! It is up to the scheduler to decide. And the results may vary depending on your operating system, JVM, and CPU.

When the loop is extended to 25 or more iterations, things begin to wobble.

The output when the loop iterates 25 times:

Thread #1 is running
Thread #1 is running
Thread #2 is running
Thread #1 is running
Thread #1 is running
Thread #2 is running
Thread #1 is running
Thread #2 is running
Thread #1 is running
Thread #1 is running
Thread #2 is running
Thread #1 is running
Thread #1 is running
Thread #2 is running
Thread #1 is running
Thread #2 is running
Thread #2 is running
Thread #1 is running
Thread #2 is running
Thread #2 is running
Thread #1 is running
Thread #2 is running
Thread #2 is running
Thread #1 is running
Thread #1 is running
Thread #2 is running
Thread #1 is running
Thread #2 is running
Thread #2 is running
Thread #1 is running
Thread #1 is running
Thread #1 is running
Thread #2 is running
Thread #1 is running
Thread #1 is running
Thread #1 is running
Thread #2 is running
Thread #2 is running
Thread #1 is running
Thread #1 is running
Thread #2 is running
Thread #1 is running
Thread #1 is running
Thread #2 is running
Thread #2 is running
Thread #2 is running
Thread #2 is running
Thread #2 is running
Thread #2 is running
Thread #2 is running

If you run this program again, you will almost certainly get a different output.

The Dark Side Of Threads (Race Condition)

E8DQ7LnupjiErqvsYs pbQ0QxupVS jpItZrXMpkSRrSiRqShNn8QhASJ2qkHEGeYCYquKHh5ZiuR e 4Xledj6Zx yP6jcza68IfnQqLUw5qvRq0fjC6Jtd MkQpKS KnbrP0w 5qb2HV2ZGHV6oFWupe 8RIEhmM7himsAbbwCb35MwnU2OErM - Concurrent Programming In Java - PART FOUR

Concurrency issues lead to race conditions. Race conditions lead to data corruption. Data corruption leads to fear… you know the rest.

It all comes down to one dangerous possibility: two or more threads having access to the data of a single object. In other words, methods invoking getters or setters on, say, a single object on the heap are called by methods running on two distinct stacks.The entire “right hand doesn’t know what the left hand is doing” issue applies here. Two threads, each believing himself to be the One True Thread, are humming along, carrying out their missions without a care in the world.   After all, a thread that is blocked or not functioning is effectively rendered comatose. It is unaware that it ever paused when it resumed as the currently-running thread.

The next example might seem very familiar if you’ve watched Friends. Please watch it if you haven’t already, simply because it is amazing. Be at ease. Whether or not you have watched the series, you will still be able to grasp the following example.

Joey and Chandler share a room. They are the best of friends. Joey is unemployed for the majority of the earlier seasons. He was hoping for a big break as an actor. Chandler had a good job. Chandler was always willing to lend Joey money.

So, how much money does Joey owe Chandler?

A whopping $120,760!

Assume they have a joint bank account. But not for long if we can’t come up with a solution. The problem? The old “two people—one bank account” scenario.

Joey and Chandler agreed that they would not overdraw the checking account. So, whoever wants to withdraw money must first check the account balance before making the withdrawal. Everything appeared to be so simple. But then they start bouncing checks and incurring overdraft fees!

They didn’t believe it was possible. They believed their procedure was risk-free.

wx2vkLd6RX57R1wa0bAdvhmbY04mSz65YdKiwojkQ7myMQp2ni90tO - Concurrent Programming In Java - PART FOUR

But then something strange happened:

Joey required $100 for acting classes. So he checked the account balance and discovered that it was $200. No worries. He intends to withdraw the funds. But first he goes to sleep!

This is where Chandler comes in. Chandler wants to withdraw $200 while Joey is still sleeping. He checks the balance, which is $200 (because Joey is still sleeping and hasn’t made his withdrawal yet). So he thinks, no problem. So he makes withdrew, and there is no problem this time. But then Joey wakes up, finishes his withdrawal, and they find themselves suddenly overdrawn! Joey had no idea he had fallen asleep, so he proceeded with his transaction without checking the balance again.

Is there a solution? Are they doomed?

We can’t stop Joey from falling asleep, but we can make sure Chandler doesn’t have access to the bank account until he wakes up.

Consider it for a moment.

The Joey And Chandler Problem, In Code

The example below demonstrates what can happen when two threads (Joey and Chandler) share a single object (the bank account).

BankAccount and JoeyAndChandlerTask are the two classes in the code. JoeyAndChandlerTask implements Runnable and represents Joey and Chandler’s behavior of checking the balance and making withdrawals. But, of course, each thread falls asleep in between checking the balance and making the withdrawal.

JoeyAndChandlerTask has an instance variable of type BankAccount that represents their joint account.

owevO3Cm8pRAo7TBt0SqWYDOUvRroTrPu3aqHI0DpKCBe6VsBFz9z 0pkbpUgBbDMa1JZnyvaiCxpK7GXDEhxqR - Concurrent Programming In Java - PART FOUR

The code works like this:

1 Make one instance of JoeyAndChandlerTask.

JoeyAndChandlerTask theTask = new JoeyAndChandlerTask();
The Runnable (the job to do) is the JoeyAndChandlerTask class. And because Joey and Chandler do the same thing (check balance and withdraw money), we only need one instance.

2 Make two threads with the same Runnable (the JoeyAndChandlerTask instance)

    Thread one = new Thread(theTask);
    Thread two = new Thread(theTask);

3 Name and start the threads

    one.setName("Joey");
    two.setName("Chandler");

    one.start();
    two.start();

4 Watch both threads execute the run() method(check the balance and make a withdrawal)

One thread represents Joey, the other represents Chandler. Both threads continually check the balance and then make a withdrawal, but only if it’s safe!

   if (account.getBalance() >= amount) {
        try {
            Thread.sleep(500);
        } catch(InterruptedException ex) {
            ex.printStackTrace();
        }
    }
Do exactly what Joey and Chandler would do in the run() method: check the balance and, if there is enough money, make the withdrawal.

This should prevent the account from being overdrawn.

Except… Joey and Chandler always fall asleep after checking the balance but before completing the withdrawal.

class BankAccount {
 
    private int balance = 100;
 
    public int getBalance() {
        return balance;
    }
 
    public void withdraw(int amount) {
        balance = balance - amount;
    }
 
}
 
public class JoeyAndChandlerTask implements Runnable {
 
    private BankAccount account = new BankAccount();
 
    public static void main (String [] args) {
 
        JoeyAndChandlerTask theTask = new JoeyAndChandlerTask();
 
        Thread one = new Thread(theTask);
        Thread two = new Thread(theTask);
 
        one.setName("Joey");
        two.setName("Chandler");
 
        one.start();
        two.start();
       
    }
 
    public void run() {
 
        for (int x = 0; x < 10; x++) {
 
            makeWithdrawal(10);
 
            if (account.getBalance() < 0) {
                System.out.println("Overdrawn!");
            }
 
        }
 
    }
 
    private void makeWithdrawal(int amount) {
 
        if (account.getBalance() >= amount) {
 
            System.out.println(Thread.currentThread().getName() + " is about to withdraw");
           
            try {
                System.out.println(Thread.currentThread().getName() + " is going to sleep");
                Thread.sleep(500);
            } catch(InterruptedException ex) {
                ex.printStackTrace();
            }
 
            System.out.println(Thread.currentThread().getName() + " woke up.");
 
            account.withdraw(amount);
 
            System.out.println(Thread.currentThread().getName() + " completes the withdrawal");
        } else {
            System.out.println("Sorry, not enough for " + Thread.currentThread().getName());
        }
 
    }
   
}

Output

Joey is about to withdraw
Chandler is about to withdraw
Chandler is going to sleep
Joey is going to sleep
Joey woke up.
Chandler woke up.
Chandler completes the withdrawal
Chandler is about to withdraw
Chandler is going to sleep
Joey completes the withdrawal
Joey is about to withdraw
Joey is going to sleep
Joey woke up.
Chandler woke up.
Chandler completes the withdrawal
Chandler is about to withdraw
Chandler is going to sleep
Joey completes the withdrawal
Joey is about to withdraw
Joey is going to sleep
Joey woke up.
Chandler woke up.
Chandler completes the withdrawal
Joey completes the withdrawal
Chandler is about to withdraw
Joey is about to withdraw
Chandler is going to sleep
Joey is going to sleep
Chandler woke up.
Chandler completes the withdrawal
Chandler is about to withdraw
Chandler is going to sleep
Joey woke up.
Joey completes the withdrawal
Joey is about to withdraw
Joey is going to sleep
Chandler woke up.
Chandler completes the withdrawal
Chandler is about to withdraw
Chandler is going to sleep
Joey woke up.
Joey completes the withdrawal
Sorry, not enough for Joey
Sorry, not enough for Joey
Sorry, not enough for Joey
Sorry, not enough for Joey
Sorry, not enough for Joey
Chandler woke up.
Chandler completes the withdrawal
Overdrawn!
Sorry, not enough for Chandler
Overdrawn!
Sorry, not enough for Chandler
Overdrawn!
Sorry, not enough for Chandler
Overdrawn!
Sorry, not enough for Chandler
Overdrawn!

Despite the fact that the makeWithdrawal() method always checks the balance before making a withdrawal, we overdraw the account.

Consider this scenario: 

Joey checks the balance, determines that there is enough money, and then falls asleep.

Meanwhile, Chandler comes in and checks the balance. He, too, sees that there is sufficient money. He has no idea Joey will wake up and complete a withdrawal. Chandler falls asleep. Joey awakens and finishes his withdrawal.

Chandler awakens and finishes his withdrawal. A major issue! Joey  awoke and drew money from the account between the times Chandler checked the balance and made the withdrawal.

Chandler’s account check was invalid because Joey had already checked and was in the middle of making a withdrawal.

Chandler must be prevented from accessing the account until Joey awakens and completes his transaction. And vice-versa.

They need a lock for account access!

The lock works like this:

image 4 - Concurrent Programming In Java - PART FOUR

image 5 - Concurrent Programming In Java - PART FOUR

image 6 - Concurrent Programming In Java - PART FOUR

What Does The Term synchronized Mean In Java?

We must ensure that once a thread enters the makeWithdrawal() method, it is allowed to complete it before any other thread can enter.

In other words, we must ensure that once a thread has checked the account balance, it will be able to wake up and complete the withdrawal before any other thread can check the account balance!

To make a method accessible to only one thread at a time, use the synchronized keyword.

That’s how you safeguard your bank account! You don’t lock the bank account; you lock the method that performs the banking transaction. That way, even if the thread falls asleep in the middle of the method, one thread gets to complete the entire transaction from start to finish!

We need the makeWithdrawal( ) method to run as one atomic thing.

So, if you don’t lock the bank account, what is locked? Is it the method? The Runnable object? What about the thread itself?

In the following section, we’ll look at that. In code, though, it’s quite simple—just add the synchronized modifier to your method declaration:

private synchronized void makeWithdrawal(int amount) {
 
        if (account.getBalance() >= amount) {
 
            System.out.println(Thread.currentThread().getName() + " is about to withdraw");
           
            try {
                System.out.println(Thread.currentThread().getName() + " is going to sleep");
                Thread.sleep(500);
            } catch(InterruptedException ex) {
                ex.printStackTrace();
            }
 
            System.out.println(Thread.currentThread().getName() + " woke up.");
 
            account.withdraw(amount);
 
            System.out.println(Thread.currentThread().getName() + " completes the withdrawal");
        } else {
            System.out.println("Sorry, not enough for " + Thread.currentThread().getName());
        }
 
}

- Concurrent Programming In Java - PART FOUR

How To Use An Object’s Lock?

Every object has a lock. The lock is usually unlocked, and you can imagine a virtual key sitting next to it. Only when there are synchronized methods do object locks come into play.

When an object has one or more synchronized methods, a thread can only enter one if the thread has the key to the object’s lock!

1RlElePU1y0XGxagw0e1cm70yJVHgJvIl SO556tkYXTa2EBX3M MR0vrxKKur y1pOZudNUy jRaanCi3tGs2klhxG9KvdCVB3 Gm7f1yh9byYaYrfRFnZa - Concurrent Programming In Java - PART FOUR

What Is The Lost Update Problem?

Here’s another classic concurrency problem from the world of databases. It’s related to the Joey and Chandler story, but we’ll use this example to highlight a few additional points.

The lost update is centered on one process:

    Step 1: Get the balance in the account
   int tempBalance = currentBalance

    

Step 2: Add 1 to that balance    
currentBalance = tempBalance + 1;

Even if we used the more common syntax currentBalance++, there is no guarantee that the resulting bytecode will be an “atomic process.” In fact, it’s unlikely.

In the “Lost Update” problem, two threads are attempting to increase the balance.

Examine the code, and then we’ll get to the real issue:

class LostUpdate implements Runnable {
 
    private int currentBalance;
 
    public void run() {
 
        for(int i = 0; i < 50; i++) {
 
            incrementBalance();
 
            System.out.println("Current balance is: " + currentBalance);
 
        }
 
    }
 
    public void incrementBalance() {
 
        /*This is the most important part! We increment the balance
        by adding 1 to the value of the balance AT THE TIME WE READ IT
        (rather than adding 1 to the current value).
        */
        int tempBalance = currentBalance; // temporarily holding the current balance
       
        currentBalance = tempBalance + 1; // incrementing and updating the current balance
 
    }
 
}
 
public class LostUpdateDemo {
 
    public static void main (String[] args) {
 
        LostUpdate task = new LostUpdate();
 
        Thread one = new Thread(task);
        Thread two = new Thread(task);
 
        one.start();
        two.start();
 
    }
 
}

Output

Current balance is: 2
Current balance is: 2
Current balance is: 3
Current balance is: 4
Current balance is: 6
Current balance is: 7
Current balance is: 8
Current balance is: 9
Current balance is: 10
Current balance is: 11
Current balance is: 12
Current balance is: 13
Current balance is: 14
Current balance is: 15
Current balance is: 16
Current balance is: 5
Current balance is: 17
Current balance is: 18
Current balance is: 19
Current balance is: 20
Current balance is: 21
Current balance is: 22
Current balance is: 23
Current balance is: 24
Current balance is: 25
Current balance is: 26
Current balance is: 27
Current balance is: 28
Current balance is: 29
Current balance is: 30
Current balance is: 31
Current balance is: 32
Current balance is: 34
Current balance is: 35
Current balance is: 36
Current balance is: 37
Current balance is: 38
Current balance is: 39
Current balance is: 40
Current balance is: 33
Current balance is: 42
Current balance is: 41
Current balance is: 43
Current balance is: 44
Current balance is: 45
Current balance is: 47
Current balance is: 46
Current balance is: 49
Current balance is: 50
Current balance is: 51
Current balance is: 52
Current balance is: 53
Current balance is: 54
Current balance is: 55
Current balance is: 48
Current balance is: 57
Current balance is: 56
Current balance is: 59
Current balance is: 58
Current balance is: 60
Current balance is: 62
Current balance is: 63
Current balance is: 64
Current balance is: 61
Current balance is: 65
Current balance is: 67
Current balance is: 68
Current balance is: 69
Current balance is: 70
Current balance is: 71
Current balance is: 66
Current balance is: 73
Current balance is: 74
Current balance is: 75
Current balance is: 72
Current balance is: 76
Current balance is: 77
Current balance is: 78
Current balance is: 79
Current balance is: 81
Current balance is: 82
Current balance is: 83
Current balance is: 80
Current balance is: 85
Current balance is: 84
Current balance is: 86
Current balance is: 87
Current balance is: 88
Current balance is: 90
Current balance is: 91
Current balance is: 92
Current balance is: 93
Current balance is: 94
Current balance is: 95
Current balance is: 96
Current balance is: 89
Current balance is: 98
Current balance is: 97
Current balance is: 99
Current balance is: 100

Let’s inspect this code…

1 Thread one runs for awhile
    Put the value of currentBalance into variable tempBalance.
    Balance is 0, so tempBalance is now 0.
    Set the value of currentBalance to the result of tempBalance + 1.
    Now the balance is 1.

    Put the value of currentBalance into variable tempBalance.
    Balance is 1, so tempBalance is now 1.
    Set the value of currentBalance to the result of tempBalance + 1.
    Now the balance is 2.

2 Thread two runs for awhile
    Put the value of currentBalance into variable tempBalance.
    Balance is 2, so tempBalance is now 2.
    Set the value of currentBalance to the result of tempBalance + 1.
    Now the balance is 3.

    Put the value of currentBalance into variable tempBalance.
    Balance is 3, so tempBalance is now 3.
   
    [now thread two is sent back to runnable,
    before it sets the value of balance to 4]

3 Thread one runs again, picking up where it left off
    Put the value of currentBalance into variable tempBalance.
    Balance is 3, so tempBalance is now 3.
    Set the value of currentBalance to the result of tempBalance + 1.
    Now the balance is 4.

    Put the value of currentBalance into variable tempBalance.
    Balance is 4, so tempBalance is now 4.
    Set the value of currentBalance to the result of tempBalance + 1.
    Now the balance is 5.

4 Thread two runs again, and pick up exactly where it left off!
    Set the value of currentBalance to the result of tempBalance + 1.
    Now the balance is 4.

    Thread one updated it to 5, but now two came back and stepped on top of the update one     made, as if one's update never happened.

    We lost the last updates made by Thread one!
   Thread two had previously read the value of currentBalance, and when two awoke, it just kept going as if it never stopped.

How To Solve A Lost Update Problem?

Create an atomic increment() method. Synchronize it!

The “Lost Update” problem is solved by synchronizing the increment() method, which keeps the two steps in the method as one unbreakable unit.

public synchronized void incrementBalance(){
 
    int tempBalance = currentBalance;
    currentBalance = tempBalance + 1;
 
}

Once a thread enters the method, we must ensure that all of the method’s steps are completed (as one atomic process) before any other thread can enter the method.

Q: To be thread-safe, it appears that synchronizing everything is a good idea.

A: No, that is not a good idea. Synchronization does not come cheap. For starters, a synchronized method has some overhead. In other words, when code encounters a synchronized method, there will be a performance hit (though you won’t notice it) while the question of “is the key available?” is resolved.

Second, because synchronization limits concurrency, a synchronized method can slow down your program. In other words, a synchronized method requires other threads to wait their turn. This may not be a problem in your code, but you should think about it.

Third, and most terrifyingly, synchronized methods can result in a deadlock!

A good rule of thumb is to synchronize only what needs to be synchronized. In fact, you can synchronize at a granularity even lower than that of a method. We don’t use it in the course, but the synchronized keyword can be used to synchronize at the finer-grained level of one or more statements rather than at the whole-method level.

What Is A DeadLock?

When using synchronized code, be cautious because thread deadlock will bring your program to its knees. Thread deadlock occurs when two threads are both holding a key that the other thread desires. Because there is no way out of this situation, the two threads will simply sit and wait. And then wait. And then wait.

If you’re familiar with databases or other application servers, you might recognize the issue. Databases frequently use a locking mechanism that’s similar to synchronization. A real transaction management system, on the other hand, can sometimes deal with deadlock. It may assume, for example, that a deadlock has occurred when two transactions take an inordinate amount of time to complete. However, unlike Java, the application server can perform a “transaction rollback” which returns the rolled-back transaction’s state to where it was before the transaction (the atomic part) began.

Java has no mechanism for dealing with deadlock. It will not even be aware that a deadlock has occurred. So it’s up to you to plan ahead of time. One of the most common pieces of advice is to keep track of the order in which your threads are started.

Share The Tutorial With Your Friends
Twiter
Facebook
LinkedIn
Email
WhatsApp
Skype
Reddit
Other Recommended Article