When two or more threads need access to a shared resource, they need some way to ensure that the resource will be used by only one thread at a time. The process by which this is achieved is called synchronization.
The key to synchronization is the concept of the monitor (also called a semaphore). A monitor is an object that is used as a mutually exclusive lock or mutex. Only one thread can own a monitor at a given time. When a thread acquires a lock, it is said to have entered the monitor.
All other threads attempting to enter the locked monitor will be suspended until the first thread exits the monitor. These other threads are said to be waiting for the monitor. A thread that owns a monitor can reenter the same monitor if it so desires.
The key to synchronization is the concept of the monitor (also called a semaphore). A monitor is an object that is used as a mutually exclusive lock or mutex. Only one thread can own a monitor at a given time. When a thread acquires a lock, it is said to have entered the monitor.
All other threads attempting to enter the locked monitor will be suspended until the first thread exits the monitor. These other threads are said to be waiting for the monitor. A thread that owns a monitor can reenter the same monitor if it so desires.
We can synchronize our code in either of two ways. Both involve the use of the synchronized keyword.
- Using Synchronized Methods
- Using Synchronized Statement Or Block
1. Using Synchronized Methods
To make a method synchronized, simply add the synchronized keyword to its declaration:
public class SynchronizedCounter {
private int c = 0;
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
public synchronized int value() {
return c;
}
}
If count is an instance of SynchronizedCounter, then making these methods synchronized has two effects:
- First, it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.
- Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads.
Let's understand in depth with one more simple example that does not use it—but should.
The following program has three simple classes CallMe, Caller, and SynchronizedMethodExample. This program is not synchronized yet and notice it's output.
package com.javaguides.javamultithreading.synchronization;
// This program is not synchronized.
class Callme {
void call(final String msg) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
} catch (final InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println("]");
}
}
class Caller implements Runnable {
protected String msg;
protected Callme target;
protected Thread t;
public Caller(final Callme targ, final String s) {
target = targ;
msg = s;
t = new Thread(this);
t.start();
}
@Override
public void run() {
target.call(msg);
}
}
public class SynchronizedMethodExample {
public static void main(final String args[]) {
final Callme target = new Callme();
final Caller ob1 = new Caller(target, "Hello");
final Caller ob2 = new Caller(target, "Synchronized");
final Caller ob3 = new Caller(target, "World");
// wait for threads to end
try {
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch (final InterruptedException e) {
System.out.println("Interrupted");
}
}
}
Output:
[Hello[Synchronized[World]
]
]
As you can see, by calling sleep( ), the call( ) method allows execution to switch to another thread. This results in the mixed-up output of the three message strings. In this program, nothing exists to stop all three threads from calling the same method, on the same object, at the same time. This is known as a race condition because the three threads are racing each other to complete the method.
This example used sleep( ) to make the effects repeatable and obvious. In most situations, a race condition is more subtle and less predictable, because you can’t be sure when the context switch will occur. This can cause a program to run right one time and wrong the next.
To fix this problem in this program, we must serialize access to call( ). That is, we must restrict its access to only one thread at a time. To do this, you simply need to proceed call( )’s definition with the keyword synchronized.
Let's rewrite the synchronized program:
package com.javaguides.javamultithreading.synchronization;
// This program is synchronized.
class Callme {
synchronized void call(final String msg) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
} catch (final InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println("]");
}
}
class Caller implements Runnable {
protected String msg;
protected Callme target;
protected Thread t;
public Caller(final Callme targ, final String s) {
target = targ;
msg = s;
t = new Thread(this);
t.start();
}
@Override
public void run() {
target.call(msg);
}
}
public class SynchronizedMethodExample {
public static void main(final String args[]) {
final Callme target = new Callme();
final Caller ob1 = new Caller(target, "Hello");
final Caller ob2 = new Caller(target, "Synchronized");
final Caller ob3 = new Caller(target, "World");
// wait for threads to end
try {
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch (final InterruptedException e) {
System.out.println("Interrupted");
}
}
}
Output:
[Hello]
[Synchronized]
[World]
2. Using Synchronized Statement or Block
While creating synchronized methods within classes that you create is an easy and effective means of achieving synchronization, it will not work in all cases.
To understand, consider that you want to synchronize access to objects of a class that was not designed for multithreaded access. That is, the class does not use synchronized methods. Further, this class was not created by you, but by a third party, and you do not have access to the source code. Thus, you can’t add synchronized to the appropriate methods within the class. How can access to an object of this class be synchronized? Fortunately, the solution to this problem is quite easy: You simply put calls to the methods defined by this class inside a synchronized block.
This is the general form of the synchronized statement:
synchronized(objRef) {
// statements to be synchronized
}
Here, objRef is a reference to the object being synchronized. A synchronized block ensures that a call to a synchronized method that is a member of objRef’s class occurs only after the current thread has successfully entered objRef’s monitor.
Here is an alternative version of the preceding example (using synchronized method ), using a synchronized block within the run( ) method:
package com.javaguides.javamultithreading.synchronization;
// This program is not synchronized.
class Callme {
void call(final String msg) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
} catch (final InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println("]");
}
}
class Caller implements Runnable {
protected String msg;
protected Callme target;
protected Thread t;
public Caller(final Callme targ, final String s) {
target = targ;
msg = s;
t = new Thread(this);
t.start();
}
@Override
synchronized public void run() {
target.call(msg);
}
}
public class SynchronizedMethodExample {
public static void main(final String args[]) {
final Callme target = new Callme();
final Caller ob1 = new Caller(target, "Hello");
final Caller ob2 = new Caller(target, "Synchronized");
final Caller ob3 = new Caller(target, "World");
// wait for threads to end
try {
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch (final InterruptedException e) {
System.out.println("Interrupted");
}
}
}
Output:
[Hello]
[World]
[Synchronized]
Here, the call( ) method is not modified by synchronized. Instead, the synchronized statement is used inside the Caller’s run() method. This causes the same correct output as the preceding example(using synchronized methods) because each thread waits for the prior one to finish before proceeding.
Comments
Post a Comment