Series index: Java Intermediate/Advanced Programming: article index
Disclaimer up front: everything in this series is my own understanding of the subject, typed out by hand. It may well contain mistaken views or misunderstandings. Use it as reference only, and corrections are welcome.
Anyone who’s touched multithreading has heard the synchronized keyword. We’ll start with a broken example to show what happens without it, fix it with synchronized, and then let synchronized lead us into deadlock. This post stays introductory; later articles will go deeper.
All demo code for this series is public at https://github.com/renfei/demo/tree/master/java/ConcurrentDemo
Setting Up the Problem
Let’s start with the broken example: what actually happens without synchronized in a multithreaded environment? We’ll model several market stalls selling apples. First a program that sells apples, 10 in total:
/**
* Apple-selling thread A: a deliberately broken demo that exposes the problem
*/
class SaleAppleA implements Runnable {
// 10 apples in total
private int apple = 10;
@Override
public void run() {
while (true) {
if (this.apple > 0) {
try {
// Simulated delay: in real code this might be network latency or business logic
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + Thread.currentThread().getName() + " sold apple number: " + this.apple--);
} else {
System.out.println("Thread " + Thread.currentThread().getName() + " sold out, closing up");
break;
}
}
}
}
Now let three sales channels start selling:
SaleAppleA saleAppleA = new SaleAppleA();
new Thread(saleAppleA, "Vendor A-A").start();
new Thread(saleAppleA, "Vendor A-B").start();
new Thread(saleAppleA, "Vendor A-C").start();
What you’ll see is the same apple number being sold repeatedly by different vendors, and some vendors closing up while others are still selling — obviously wrong. Each apple should be sold exactly once, and once stock runs out everyone should close, rather than some closing while others keep going.
That’s the synchronization problem in a multithreaded environment. When one vendor takes an apple, the others don’t know about it and can still take the same one. So we need a mechanism guaranteeing that once an apple is taken it’s really gone, instead of being taken repeatedly.
Thread Synchronization
To fix the problem above, Java gives us the synchronized keyword. I won’t dig into the details here — there’s a dedicated post coming on synchronized as an atomic intrinsic lock.
Here’s a new SaleAppleB, where the only change is adding synchronized to the sale method:
/**
* Apple-selling thread B, with synchronization added
*/
class SaleAppleB implements Runnable {
// 10 apples in total
private int apple = 10;
private synchronized boolean sale() {
if (this.apple > 0) {
try {
// Simulated delay: in real code this might be network latency or business logic
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + Thread.currentThread().getName() + " sold apple number: " + this.apple--);
return false;
} else {
System.out.println("Thread " + Thread.currentThread().getName() + " sold out, closing up");
return true;
}
}
@Override
public void run() {
while (true) {
if (this.sale()) {
break;
}
}
}
}
Set up a few vendors and try again:
SaleAppleB saleAppleB = new SaleAppleB();
new Thread(saleAppleB, "Vendor B-A").start();
new Thread(saleAppleB, "Vendor B-B").start();
new Thread(saleAppleB, "Vendor B-C").start();
The full demo lives at https://github.com/renfei/demo/blob/master/java/ConcurrentDemo/src/main/java/net/renfei/demo/concurrent/ThreadSynchronizationDemo.java
Everything is harmonious now — no double-selling, because with synchronized only one thread can be inside the sale method at any moment.
synchronized is a fairly heavy lock: once a thread enters a synchronized method, the whole SaleAppleB object is locked and only one thread gets in at a time. Used carelessly, that opens the door to another problem: deadlock.
Deadlock
Let me show you one. My example here is again about buying apples. We define two resource classes, Money and Apple, with this logic:
Money: give me the apple first, and I’ll give you money.
Apple: give me the money first, and I’ll give you the apple.
The code:
/**
* Threads holding each other's resources — a deadlock demo
*
* @author renfei
*/
public class DeadlockDemo implements Runnable {
private Money money = new Money();
private Apple apple = new Apple();
public static void main(String[] args) {
new DeadlockDemo();
}
/**
* The main thread, at construction time, holds Money and needs Apple before paying
*/
public DeadlockDemo() {
new Thread(this).start();
money.buy(apple);
}
/**
* The child thread holds Apple and needs Money before it can sell
*/
@Override
public void run() {
apple.sale(money);
}
}
/**
* The Money resource
*/
class Money {
/**
* Buy an apple
*
* @param apple the apple
*/
public synchronized void buy(Apple apple) {
System.out.println("Thread " + Thread.currentThread().getName() + ": give me the apple, and I'll give you money");
apple.ok();
}
public synchronized void ok() {
System.out.println("Thread " + Thread.currentThread().getName() + ": apple purchased successfully.");
}
}
/**
* The Apple resource
*/
class Apple {
/**
* Sell an apple
*
* @param money the money
*/
public synchronized void sale(Money money) {
System.out.println("Thread " + Thread.currentThread().getName() + ": give me the money, and I'll give you the apple");
money.ok();
}
public synchronized void ok() {
System.out.println("Thread " + Thread.currentThread().getName() + ": apple sold successfully.");
}
}
Full source at https://github.com/renfei/demo/blob/master/java/ConcurrentDemo/src/main/java/net/renfei/demo/concurrent/DeadlockDemo.java
Run it and you’ll see the program hang: Money is waiting for Apple to release its resource while Apple is waiting for Money. Deadlock means two parties each hold a resource the other needs, so they wait forever.
It doesn’t have to be exactly two resources holding each other though. Any set of resources forming a circular hold chain can deadlock, which also means breaking the cycle is enough to break the deadlock.
