Java Game Developing
Java Game Developing

Java Threads

What Is a Thread?

Imagine the multitasking waiter is your computer's processor and the customers are tasks. Each task runs in its own thread, and a processor with a modern operating system can run many threads concurrently. For example, you've probably downloaded a file from the Internet while writing a paper at the same time.

Modern operating systems run threads concurrently by splitting a thread's task into smaller chunks. This is called concurrency. One thread is executed for a small amount of time (time slices). Then the thread is pre-empted, enabling another thread to run, and so on. The time slices are small enough so that it seems as if several things are happening at once.

Creating and Running Threads in Java

Java was designed with threads in mind, so you'll find it easier to work with threads in Java than in many other languages. To create and start a new thread, just create an instance of the Thread object and call the start() method:

Thread myThread = new Thread();

myThread.start();

Of course, this code won't do anything useful because the thread isn't assigned a task. The JVM creates a new system thread, starts it, and calls the Thread object's run() method. By default, the run() method doesn't do anything, so the thread dies.

If you want to give a thread a task, and I'm sure you do, give the run() method something to do. You can do this in three basic ways:

  • Extend the Thread class
  • Implement the Runable interface 
  • Use anonymous inner classes

 

Extending the Thread Class

A quick way to give a thread a task is simply to extend the Thread class and override the run() method:

public class MyThread extends Thread {


    public void run() {

        System.out.println("Do something cool here.");

    }

}

Then create and start the thread the same way as before:

Thread myThread = new MyThread();

myThread.start();

Now you've got two threads running: the main thread and the thread you just started.

Implementing the Runnable Interface

Extending the Thread class is easy, but most of the time you probably don't want to write a new class just to start a thread. For example, you might want a class that extends another class and can also be run as a thread. In this case, implement the Runnable interface:

public class MyClass extends SomeOtherClass implements Runnable {

 

    public MyClass() {

        Thread thread = new Thread(this);

        thread.start();

    }

    public void run() {

        System.out.println("Do something cool here.");

    }

}

In this example, the MyClass object starts a new thread on construction. The Thread class takes a Runnable object as a parameter in its constructor, and that Runnable is executed when the thread is started.

Using Anonymous Inner Classes

Sometimes you want to spawn a new thread without the bother of creating a new class, or perhaps it's not convenient to implement the Runnable interface. In this case, you can use an anonymous inner class to start a new thread:

new Thread() {

    public void run() {

        System.out.println("Do something cool here.");

    }

}.start();

This example is simple enough, but it can quickly become unreadable if the code in the run() method is too long. Use this one sparingly.

Waiting for a Thread to Finish

If you want your current thread to wait until a thread is finished, use the join() method:

myThread.join();

This could be useful when a player exits your game, when you want to make sure all threads are finished before you do any cleanup.

Sleepy Threads

Sometimes your threads might get tired, and you'll be nice enough to give them a break. Now you're thinking, "What? My threads get tired? This is too complicated!"

No, your threads don't really get tired. But sometimes you might need a thread to pause for a bit, so use the static sleep() method:

Thread.sleep(1000);

This causes the currently running thread to sleep for 1000 milliseconds, or any amount of time you choose. A sleeping thread doesn't consume any CPU time—so it doesn't even dream.

Synchronization

Great, now you've got some threads running and they're doing all sorts of cool things at once. It's not all sunshine and lollipops, though—if you've got multiple threads trying to access the same objects or variables, you can run into synchronization problems.

Avoiding Deadlock

Deadlock is the result of two threads that stall because they are waiting on each other to do something. Consider this example:

  • Thread A acquires lock 1.                     
  • Thread B acquires lock 2. 
  • B Thread waits for lock 1 to be released.
  • A Thread waits for lock 2 to be released
As you can see, both threads are waiting on the lock that the other has, so they both stall indefinitely.
Deadlock can occur as soon as you have multiple threads trying to acquire multiple locks out of order.
How do you avoid deadlock? The best way is to simply write your synchronization code in such a way that it cannot occur. Carefully try to consider every possibility.

Using wait() and notify()

Let's say you have two threads and they need to talk to each other. For example, let's say Thread A is waiting on Thread B to send a message:

// Thread A

public void waitForMessage() {

    while (hasMessage == false) {

        Thread.sleep(100);

    }

}

 

// Thread B

public void setMessage(String message) {

    ...

    hasMessage = true;

}

Although this works, it's a clunky solution. Thread A has to constantly check whether Thread B has sent the message every 100 milliseconds, or 10 times a second. Thread A could oversleep and be late in getting the message. Also, what would happen if several threads were waiting for a message? Wouldn't it be nice if Thread A could just stay idle and be notified when Thread B sends the message?

Lucky for us, the wait() and notify() methods provide this capability.

The wait() method is used in synchronized blocks of code. When the wait() method executes, the lock is released and the thread waits to be notified.

The notify() method is also used in a synchronized block of code. The notify() method notifies one thread waiting on the same lock. If several threads are waiting on the lock, one of them is notified randomly.

Here's the updated message code:

// Thread A

public synchronized void waitForMessage() {

    try {

        wait();

    }

    catch (InterruptedException ex) { }

}

 

// Thread B

public synchronized void setMessage(String message) {

    ...

    notify();

}

After Thread B calls notify() and leaves its synchronized method (releasing the lock on this), Thread A reacquires the lock and finishes its synchronized block of code. In this case, it just returns.

You can also choose to wait for a maximum amount of time. The wait() method can take a maximum amount of time to wait as a parameter:

wait(100);

If the thread is never notified, this is equivalent to putting the thread to sleep for the specified amount of time. Unfortunately, there's no way to tell whether the wait() method returned because of a timeout or because the thread was notified.

A second notify method, notifyAll(), notifies all threads waiting on the lock, instead of just one.

The wait(), notify(), and notifyAll() methods are methods of the Object class, so any Java object has these methods—just like any Java object can be used as a lock.