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.

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):

  1. Starting off, the object's variables are playerX = 1 and playerY = 0.

  2. Thread A calls setPosition(0,1).

  3. The line playerX = x; is executed. Now playerX = 0.

  4. Thread A is pre-empted by Thread B.

  5. Thread B calls isAtExit().

  6. Currently, playerX = 0 and playerY = 0, 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. So, if one synchronized method owns a lock, no other synchronized method can run until the lock is released. You can think of locks as a lock on a restroom door. Only one person is in the restroom at a time. The door is unlocked when the person leaves or, in the case of an exception, when the person jumps out the window. Besides method synchronization, there's also object synchronization. Object synchronization enables you to treat any object as the lock—with method synchronization, the current instance (this) is the lock. Method synchronization is essentially shorthand for object synchronization with this. For example, from the previous maze code, the method

public synchronized void setPosition(int x, int y) {
 playerX = x;
 playerY = y;
}


is essentially the same as this:

public void setPosition(int x, int y) {
 synchronized(this) {
 playerX = x;
 playerY = y;
 }
}


The only exception is that the second example has some extra bytecode instructions. Object synchronization is useful when you need more than one lock, when you need to acquire a lock on something other than this, or when you don't need to synchronize an entire method. A lock can be any object, even arrays—basically, anything except primitive types. If you need to roll your own lock, just create a plain Object:

Object myLock = new Object();
...
synchronized (myLock) {
 ...
}


What to Synchronize

But what should you synchronize? The answer is any time two or more threads will access the same object or field.

What Not to Synchronize

When synchronizing your code, the general rule is not to oversynchronize—don't synchronize more code than you have to. Doing so creates unnecessary delays when two or more threads try to execute the same synchronized block of code. For example, don't synchronize an entire method if only parts of the method need to be synchronized. Instead, just put a synchronized block around the critical parts of code:

public void myMethod() {
 synchronized(this) {
 // code that needs to be synchronized
 }
 // code that is already thread-safe
}


Also, you don't have to synchronize a method that uses only local variables. Local variables are stored on the stack, and each thread has its own separate stack, so there isn't a chance of running into synchronization problems. For example, this method doesn't need to be synchronized because it uses only local variables:

public int square(int n) {
 int s = n * n;
 return s;
}


Finally, don't worry about synchronizing code that isn't accessed by multiple threads. However, if you know that some code is accessed by only one thread and you don't synchronize, be sure to mention in your JavaDoc comments that the code isn't thread-safe. By doing this, you'll know you have to change it if multiple threads in a future version access that code. If you're not sure what threads are accessing your code, just print the name of the currently running thread to the console:

System.out.println(Thread.currentThread().getName());


Avoiding Deadlock

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

  1. Thread A acquires lock 1.

  2. Thread B acquires lock 2.

  3. Thread B waits for lock 1 to be released.

  4. Thread A 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. Think about which threads acquire which locks and in what order. Carefully try to consider every possibility. If you think your game might be stalling because of deadlock, there are ways to detect it. As of version 1.4.1 of the HotSpot VM, Sun has provided a deadlock detector. Run your game from a console, and when it stalls, press Ctrl+\ (or Ctrl+break, in Windows). The VM displays information on the state of threads, whether they are waiting on anything, and whether deadlock is detected.



   
Comments