Chapter 1. Java Threads
KEY TOPICS
It's lunchtime, and you decide to treat yourself to a meal at your favorite restaurant. You take a seat and look aroundthe place is pretty empty. There's one waiter, one customer eating, and you.
The waiter is helping the other customer, but oddly, you're being ignored. Even when you ask for a menu, the waiter acts as if you're not there and just refills the other customer's beverage. Finally, after the other customer leaves, the waiter acknowledges your existence and helps you.
So what is the problem? The waiter hasn't been fired, that's the problem.
Actually, the problem is that the waiter didn't multitask . A multitasking waiter could serve two or more customers at once instead of waiting for one customer to finish before serving a new customer.
The multitasking waiter is a rough analogy to how threads work in computer science. In this chapter, we go over all about how Java threads work and how to synchronize them, and we teach you the tips and tricks along the way.
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, as shown in
Figure 1.1. Concurrency means running multiple threads on one processor.
On machines with more than one processor, threads might actually run simultaneously, depending on the JVM implementation.
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:
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 timeso 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, thoughif you've got multiple threads trying to access the same objects or variables, you can run into synchronization problems.
Why Synchronize?
Let's say you're creating a maze game. Any thread can set the position of the player, and any thread can check to see if the player is at the exit. For simplicity, let's say the exit is at position x = 0, y = 0.
public class Maze { private int playerX; private int playerY; public boolean isAtExit() { return (playerX == 0 && playerY == 0); } public void setPosition(int x, int y) { playerX = x; playerY = y; }}
Most of the time, this code works fine. But keep in mind that threads can be pre-empted at any time. Imagine this scenario, in which the player moves from (1,0) to (0,1):
Starting off, the object's variables are playerX = and playerY = .
Thread A calls setPosition(0,1) .
The line playerX = x; is executed. Now playerX = .
Thread A is pre-empted by Thread B.
Thread B calls isAtExit() .
Currently, playerX = and playerY = , so isAtExit() returns true !
In this scenario, the player is reported as solving the maze when it's not the case. To fix this, you need to make sure the setPosition() and isAtExit() methods can't execute at the same time.
How to Synchronize
Enter the synchronized keyword. In the maze example, if you make the methods synchronized, only one method can run at a time.
Here's the updated, thread-safe code:
public class Maze { private int playerX; private int playerY; public synchronized boolean isAtExit() { return (playerX == 0 && playerY == 0); } public synchronized void setPosition(int x, int y) { playerX = x; playerY = y; }} When the JVM executes a synchronized method, it acquires a lock on that object. Only one lock can be acquired on an object at a time. The lock is released when the method is finished executing either by returning normally or by throwing an exception.