Synchronize access to shared
mutable data
1
An important fact about mutable data and
concurrency in Java
• An update on mutable data made by one thread may not be visible by
other threads unless an appropriate synchronization is used.
• Such unsynchronized shared mutable data can cause nasty problems.
2
Example: a thread that never sees an update
3
class ThisNeverFinishesOnMyLaptop {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread backgroundThread = new Thread(() -> {
while (true) { // wait until the counter gets incremented
if (counter.getCount() != 0) break; // this never breaks; it becomes an infinite loop
}
System.out.println("Finished");
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
counter.increment();
}
}
class Counter {
int count = 0; // this is mutable data
int getCount() { return count; }
int increment() { return ++count; }
}
Code based on the example from Effective Java, 3rd edition p.312
Synchronization with synchronized blocks
4
class NowItFinishesOnMyLaptop {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Object lock = new Object();
Thread backgroundThread = new Thread(() -> {
while (true) { // wait until the counter gets incremented
synchronized (lock) {
if (counter.getCount() != 0) break; //now it works
}
}
System.out.println("Finished");
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
synchronized (lock) {
counter.increment();
}
}
}
Synchronization with volatile
5
class ThisAlsoFinishesOnMyLaptop {
public static void main(String[] args) throws InterruptedException {
VolatileCounter counter = new VolatileCounter();
Thread backgroundThread = new Thread(() -> {
while (true) { // wait until the counter gets incremented
if (counter.getCount() != 0) break; //now it works too
}
System.out.println("Finished");
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
counter.increment();
}
}
class VolatileCounter {
volatile int count = 0;
int getCount() { return count; }
int increment() { return ++count; }
}
A limitation of volatile: it doesn’t guarantee
atomicity
6
class IncrementByMultipleThreads {
public static void main(String[] args) {
VolatileCounter counter = new VolatileCounter();
Set<Integer> ints = Collections.synchronizedSet(new HashSet<>());
Runnable incrementer = () -> {
while (true) {
int increment = counter.increment();
boolean added = ints.add(increment);
if (!added) System.out.println("duplicate number detected");//volatile doesn’t prevent this
}
};
Thread t1 = new Thread(incrementer), t2 = new Thread(incrementer), t3 = new Thread(incrementer);
t1.start(); t2.start(); t3.start();
}
}
Synchronization with synchronized blocks
guarantees exclusive code execution
7
class IncrementByMultipleThreadsWithLock {
public static void main(String[] args) {
Counter counter = new Counter();
Object lock = new Object();
Set<Integer> ints = Collections.synchronizedSet(new HashSet<>());
Runnable incrementer = () -> {
while (true) {
int increment;
synchronized (lock) {
increment = counter.increment();
}
boolean added = ints.add(increment);
if (!added) System.out.println("duplicate number detected"); //this doesn’t happen anymore
}
};
Thread t1 = new Thread(incrementer), t2 = new Thread(incrementer), t3 = new Thread(incrementer);
t1.start(); t2.start(); t3.start();
}
}
Which classes in JDK need a synchronization
in concurrent use cases?
• HashMap
• It can trigger an infinite loop in some cases, which is extremely dangerous.
• https://mailinator.blogspot.com/2009/06/beautiful-race-condition.html
• Thread-safe equivalent: ConcurrentHashMap
• ArrayList
• Thread-safe equivalent: CopyOnWriteArrayList
• SimpleDateFormat
• Oftentimes it’s stored in a public static final field; dangerous
• Thread-safe equivalent: DateTimeFormatter
• DecimalFormat, MessageFormat, etc.
• Many more
8
Note: Just putting volatile doesn’t make those classes thread-safe.
Conclusion
• We have discussed:
• Why we need to synchronize shared mutable data
• How we can do that
• What happens when we fail to do that
• The consequences of missing synchronization can be horrible:
• Triggering an infinite loop
• Getting an invalid result or a mysterious exception
• Any sort of totally unpredictable chaos
• Minimize mutability. Immutable data is always thread-safe.
• Further reading: Java Concurrency in Practice
9

Synchronize access to shared mutable data

  • 1.
    Synchronize access toshared mutable data 1
  • 2.
    An important factabout mutable data and concurrency in Java • An update on mutable data made by one thread may not be visible by other threads unless an appropriate synchronization is used. • Such unsynchronized shared mutable data can cause nasty problems. 2
  • 3.
    Example: a threadthat never sees an update 3 class ThisNeverFinishesOnMyLaptop { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Thread backgroundThread = new Thread(() -> { while (true) { // wait until the counter gets incremented if (counter.getCount() != 0) break; // this never breaks; it becomes an infinite loop } System.out.println("Finished"); }); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); counter.increment(); } } class Counter { int count = 0; // this is mutable data int getCount() { return count; } int increment() { return ++count; } } Code based on the example from Effective Java, 3rd edition p.312
  • 4.
    Synchronization with synchronizedblocks 4 class NowItFinishesOnMyLaptop { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Object lock = new Object(); Thread backgroundThread = new Thread(() -> { while (true) { // wait until the counter gets incremented synchronized (lock) { if (counter.getCount() != 0) break; //now it works } } System.out.println("Finished"); }); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); synchronized (lock) { counter.increment(); } } }
  • 5.
    Synchronization with volatile 5 classThisAlsoFinishesOnMyLaptop { public static void main(String[] args) throws InterruptedException { VolatileCounter counter = new VolatileCounter(); Thread backgroundThread = new Thread(() -> { while (true) { // wait until the counter gets incremented if (counter.getCount() != 0) break; //now it works too } System.out.println("Finished"); }); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); counter.increment(); } } class VolatileCounter { volatile int count = 0; int getCount() { return count; } int increment() { return ++count; } }
  • 6.
    A limitation ofvolatile: it doesn’t guarantee atomicity 6 class IncrementByMultipleThreads { public static void main(String[] args) { VolatileCounter counter = new VolatileCounter(); Set<Integer> ints = Collections.synchronizedSet(new HashSet<>()); Runnable incrementer = () -> { while (true) { int increment = counter.increment(); boolean added = ints.add(increment); if (!added) System.out.println("duplicate number detected");//volatile doesn’t prevent this } }; Thread t1 = new Thread(incrementer), t2 = new Thread(incrementer), t3 = new Thread(incrementer); t1.start(); t2.start(); t3.start(); } }
  • 7.
    Synchronization with synchronizedblocks guarantees exclusive code execution 7 class IncrementByMultipleThreadsWithLock { public static void main(String[] args) { Counter counter = new Counter(); Object lock = new Object(); Set<Integer> ints = Collections.synchronizedSet(new HashSet<>()); Runnable incrementer = () -> { while (true) { int increment; synchronized (lock) { increment = counter.increment(); } boolean added = ints.add(increment); if (!added) System.out.println("duplicate number detected"); //this doesn’t happen anymore } }; Thread t1 = new Thread(incrementer), t2 = new Thread(incrementer), t3 = new Thread(incrementer); t1.start(); t2.start(); t3.start(); } }
  • 8.
    Which classes inJDK need a synchronization in concurrent use cases? • HashMap • It can trigger an infinite loop in some cases, which is extremely dangerous. • https://mailinator.blogspot.com/2009/06/beautiful-race-condition.html • Thread-safe equivalent: ConcurrentHashMap • ArrayList • Thread-safe equivalent: CopyOnWriteArrayList • SimpleDateFormat • Oftentimes it’s stored in a public static final field; dangerous • Thread-safe equivalent: DateTimeFormatter • DecimalFormat, MessageFormat, etc. • Many more 8 Note: Just putting volatile doesn’t make those classes thread-safe.
  • 9.
    Conclusion • We havediscussed: • Why we need to synchronize shared mutable data • How we can do that • What happens when we fail to do that • The consequences of missing synchronization can be horrible: • Triggering an infinite loop • Getting an invalid result or a mysterious exception • Any sort of totally unpredictable chaos • Minimize mutability. Immutable data is always thread-safe. • Further reading: Java Concurrency in Practice 9