147 lines
3.5 KiB
Java
147 lines
3.5 KiB
Java
// lowlevel/CriticalSection.java
|
|
// (c)2017 MindView LLC: see Copyright.txt
|
|
// We make no guarantees that this code is fit for any purpose.
|
|
// Visit http://OnJava8.com for more book information.
|
|
// (Behavior may have changed in Java 8)
|
|
// Synchronizing blocks instead of entire methods. Also
|
|
// demonstrates protection of a non-thread-safe class
|
|
// with a thread-safe one.
|
|
// {java threads.CriticalSection}
|
|
package threads;
|
|
import java.util.concurrent.*;
|
|
import java.util.concurrent.atomic.*;
|
|
import java.util.*;
|
|
import onjava.*;
|
|
|
|
class Pair { // Not thread-safe
|
|
private int x, y;
|
|
public Pair(int x, int y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
public Pair() { this(0, 0); }
|
|
public int getX() { return x; }
|
|
public int getY() { return y; }
|
|
public void incrementX() { x++; }
|
|
public void incrementY() { y++; }
|
|
@Override
|
|
public String toString() {
|
|
return "x: " + x + ", y: " + y;
|
|
}
|
|
public class PairValuesNotEqualException
|
|
extends RuntimeException {
|
|
public PairValuesNotEqualException() {
|
|
super("Pair values not equal: " + Pair.this);
|
|
}
|
|
}
|
|
// Arbitrary invariant -- both variables must be equal:
|
|
public void checkState() {
|
|
if(x != y)
|
|
throw new PairValuesNotEqualException();
|
|
}
|
|
}
|
|
|
|
// Protect a Pair inside a thread-safe class:
|
|
abstract class PairManager {
|
|
AtomicInteger checkCounter = new AtomicInteger(0);
|
|
protected Pair p = new Pair();
|
|
private List<Pair> storage =
|
|
Collections.synchronizedList(new ArrayList<>());
|
|
public synchronized Pair getPair() {
|
|
// Make a copy to keep the original safe:
|
|
return new Pair(p.getX(), p.getY());
|
|
}
|
|
// Assume this is a time consuming operation
|
|
protected void store(Pair p) {
|
|
storage.add(p);
|
|
new Nap(50);
|
|
}
|
|
public abstract void increment();
|
|
}
|
|
|
|
// Synchronize the entire method:
|
|
class PairManager1 extends PairManager {
|
|
@Override
|
|
public synchronized void increment() {
|
|
p.incrementX();
|
|
p.incrementY();
|
|
store(getPair());
|
|
}
|
|
}
|
|
|
|
// Use a critical section:
|
|
class PairManager2 extends PairManager {
|
|
@Override
|
|
public void increment() {
|
|
Pair temp;
|
|
synchronized(this) {
|
|
p.incrementX();
|
|
p.incrementY();
|
|
temp = getPair();
|
|
}
|
|
store(temp);
|
|
}
|
|
}
|
|
|
|
class PairManipulator implements Runnable {
|
|
private PairManager pm;
|
|
public PairManipulator(PairManager pm) {
|
|
this.pm = pm;
|
|
}
|
|
@Override
|
|
public void run() {
|
|
while(true)
|
|
pm.increment();
|
|
}
|
|
@Override
|
|
public String toString() {
|
|
return "Pair: " + pm.getPair() +
|
|
" checkCounter = " + pm.checkCounter.get();
|
|
}
|
|
}
|
|
|
|
class PairChecker implements Runnable {
|
|
private PairManager pm;
|
|
public PairChecker(PairManager pm) {
|
|
this.pm = pm;
|
|
}
|
|
@Override
|
|
public void run() {
|
|
while(true) {
|
|
pm.checkCounter.incrementAndGet();
|
|
pm.getPair().checkState();
|
|
}
|
|
}
|
|
}
|
|
|
|
public class CriticalSection {
|
|
// Test the two different approaches:
|
|
static void
|
|
testApproaches(PairManager pman1, PairManager pman2) {
|
|
ExecutorService es = Executors.newCachedThreadPool();
|
|
PairManipulator
|
|
pm1 = new PairManipulator(pman1),
|
|
pm2 = new PairManipulator(pman2);
|
|
PairChecker
|
|
pcheck1 = new PairChecker(pman1),
|
|
pcheck2 = new PairChecker(pman2);
|
|
es.execute(pm1);
|
|
es.execute(pm2);
|
|
es.execute(pcheck1);
|
|
es.execute(pcheck2);
|
|
new Nap(500);
|
|
System.out.println("pm1: " + pm1 + "\npm2: " + pm2);
|
|
System.exit(0);
|
|
}
|
|
public static void main(String[] args) {
|
|
new TimedAbort(4);
|
|
PairManager
|
|
pman1 = new PairManager1(),
|
|
pman2 = new PairManager2();
|
|
testApproaches(pman1, pman2);
|
|
}
|
|
}
|
|
/* Output:
|
|
TimedAbort 4
|
|
*/
|