Java programming course: 12.2 Extending the Thread class

In the previous lesson we learnt about the concepts of multithreading.


Extending the Thread class

The first way of defining a multithreaded class is to extend the Thread class and override its run() method. Here is the Booker class which does just that:

package virtualzoo.core;

public class Booker extends Thread {
    
    private BookingCounter booking;
    
    public Booker(BookingCounter courseing) {
        this.booking = booking;
    }
    
    @Override
    public void run() {
        booking.makeBooking();
    }
    
}
 
  • Each object of type Booker needs a reference to the BookingCounter object, which it gets in its constructor
  • The run() method defined in Thread does nothing, so you need to override it to do something useful. Here, you simply invoke the makeBooking() method of the referencedBookingCounter object

Here is the code needed to simulate 100 separate persons (each of whom will be a threaded object of the Booker class) each making one booking. You could enter this in the Experiments class to run it:

// Create a single BookingCounter
BookingCounter booking = new BookingCounter();
        
// Create 100 Booker threads using same BookingCounter
for (int i = 0; i < 100; i++) {
    Booker b = new Booker(courseing);
    b.start();
}
        
// Allow time for all threads to complete
try {
    Thread.sleep(1000); // 1000 milliseconds = 1 second
} catch (InterruptedException ex) {
    System.out.println(ex.getMessage());
}
System.out.println("Number of bookings  made: " + booking.getCount());
 
  • An instance of BookingCounter is created
  • Then, 100 separate Booker objects are created in a for loop. Note the call to a method called start() (which is inherited from Thread). It is the start() method which sets up the object in its own thread and which invokes the run() method for you, which you overrode in the Booker class. If you were to call run() yourself instead of calling start() then while your code would execute it would not do so in its own thread
  • The current thread is put to sleep for 1000 milliseconds (which is 1 second) in order to give all of the threads time to complete
  • Finally, the total number of bookings made is sent to the Output window

The number of bookings output should of course be 100, since that was how many Booker objects made a single booking each. But if you run the above code, you will almost certainly find it outputs a number smaller than 100, possibly much smaller. It is also possible that you will get different results each time you run it. So, what is causing this to happen? To answer this question, it will be helpful to see diagrammatically how a thread transitions between its various states:

Transition between thread states

The above diagram looks a little daunting at first, but here is a descriptive summary:

  • The new keyword is used to instantiate a thread (just as it is used to instantiate any object). This puts the thread into the initial state, which means it just exists
  • When the start() method is invoked it moves into the runnable state. This means it is eligible to begin executing but is not currently doing so. It will wait until the JVM scheduler gives it some time
  • When the scheduler decides to give it some time, it goes into your run() method and the thread goes into the running state, meaning it is actually doing something
  • Because the scheduler tries to share time across multiple threads it may decide to pause part way inside the run() method of the running thread (which is in effect a "time-out"). It will then move the thread back into the runnable state while one of the other thread or threads are given time. If instead of a time-out the running thread invokes a sleep(), wait() or input/output process it will move into the blocked state before going back into the runnable state
  • At some point the scheduler will give time back to the thread where it will resume where it left off. The thread may need to switch between running and runnable (or blocked) many times before it finally completes
  • When the run() method ends the thread moves into the finished state

Now look again at the pertinent code inside the makeBooking() method of BookingCounter:

int copyCount = count;	// 1
copyCount++;			// 2
count = copyCount;		// 3
 

Suppose there are only two separate Booker threads that each need to execute the above code. When the first thread moves into the running state it needs to execute statements 1, 2 and 3 above but it is possible that it might only complete statement 1 before the scheduler decides to pause it, giving control to the second thread. At this stage, the count variable has not had a chance to be incremented so will be at its initial value of zero.

Now, the second thread starts and when it executes statement 1 finds that count is still zero. Suppose it then goes on to complete statements 2 and three, leaving count with the value of 1.

Now, the scheduler passes control back to the first thread where it will carry on from where it left off, but its object copyCount is still zero so it when it completes statements 2 and 3 count ends up being 1 again, instead of 2 which it should be.

You might think that replacing the above three statements with the one below would solve the problem:

count++; 

However, a single Java statement could still require multiple machine code instructions to do its work, and the scheduler might pause it before they complete. Taking the incrementation above, the machine code still needs three processes to achieve this:

  1. Read the current value from memory
  2. Update the value
  3. Write the new value back to memory

You may find that using count++ as the only statement inside makeBooking() appears to work – but you cannot guarantee that it always will.

The solution is to lock the object such that other threads won't be allowed to gain access until it completes. To do this you need to specify the synchronized keyword as part of the method definition:

public synchronized int getCount() {
    return count;
}

public synchronized void makeBooking() {
    // Get copy of the current count
    int copyCount = count;
        
    // Add one to the count copy
    copyCount++;
        
    // Sleep for 1 millisecond to "force" another thread to run
    try {
        Thread.sleep(1);
    } catch (InterruptedException ex) {
        System.out.println(ex.getMessage());
    }
        
    // Update the actual count from the copy
    count = copyCount;
}
 

Once the first thread gains access to a synchronized method it puts a lock on the object the method belongs to, such that no other thread can access any synchronized method in that object until the first thread completes. In this way, you know the count variable will be updated properly for each thread, and you should now find that executing the code results in the correct output of 100.

To summarise, any method which returns or changes an instance variable, and which could be invoked by multiple threads, should be marked as synchronized. This ensures that a lock is placed upon the object by the invoking thread and the lock only gets released when that thread completes all of the code within the method. This means that no other thread will be able to access either that or any other synchronized method until the lock is released. Methods which aren't marked as synchronized will still be available to other threads.

Use of synchronisation does have a performance impact, so you should avoid using the synchronized keyword if you know it can never be invoked by multiple threads.

It is also possible to synchronize blocks of code within a method rather than an entire method.


In the next lesson we will show how to create a thread through implementing the Runnable interface.

Next lesson: 12.3 Implementing the Runnable interface


Print
×
Stay Informed

When you subscribe, we will send you an e-mail whenever there are new updates on the site.

Related Posts

 

Comments

No comments made yet. Be the first to submit a comment
Monday, 27 October 2025

Captcha Image