Java编程思想第4版P1173 ExplicitCriticalSection同步控制块问题及解决方法

运行时报错,线程没有达到同步的效果(x和y值本来应该相等)

Exception in thread "pool-1-thread-4" concurrency.Pair$PairValuesNotEqualException: Pair values not equal: x: 2, y: 1
    at concurrency.Pair.checkState(CriticalSection.java:34)
    at concurrency.PairChecker.run(CriticalSection.java:111)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:619)
pm1: Pair: x: 13, y: 13 checkCounter = 4
pm2: Pair: x: 14, y: 14 checkCounter = 1047068

原来的代码:

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<Pair>());
  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);
    try {
      TimeUnit.MILLISECONDS.sleep(50);
    } catch(InterruptedException ignore) {}
  }
  public abstract void increment();
}


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 exec = Executors.newCachedThreadPool();
    PairManipulator
      pm1 = new PairManipulator(pman1),
      pm2 = new PairManipulator(pman2);
    PairChecker
      pcheck1 = new PairChecker(pman1),
      pcheck2 = new PairChecker(pman2);
    exec.execute(pm1);
    exec.execute(pm2);
    exec.execute(pcheck1);
    exec.execute(pcheck2);
    try {
      TimeUnit.MILLISECONDS.sleep(500);
    } catch(InterruptedException e) {
      System.out.println("Sleep interrupted");
    }
    System.out.println("pm1: " + pm1 + "\npm2: " + pm2);
    System.exit(0);
  }
 
} 

class ExplicitPairManager1 extends PairManager {
  private Lock lock = new ReentrantLock();
  @Override
public synchronized void increment() {
    lock.lock();
    try {
      p.incrementX();
      p.incrementY();
      store(getPair());
    } finally {
      lock.unlock();
    }
  }
}

// Use a critical section:
class ExplicitPairManager2 extends PairManager {
  private Lock lock = new ReentrantLock();
  @Override
public void increment() {
    Pair temp;
    lock.lock();
    try {
      p.incrementX();
      p.incrementY();
      temp = getPair();
    } finally {
      lock.unlock();
    }
    store(temp);
  }
}

public class ExplicitCriticalSection {
  public static void main(String[] args) throws Exception {
    PairManager
      pman1 = new ExplicitPairManager1(),
      pman2 = new ExplicitPairManager2();
    CriticalSection.testApproaches(pman1, pman2);
  }
} 

修改后的代码:

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){
	    	System.out.println(this);
	      throw new PairValuesNotEqualException();
	    }
  }
}

// Protect a Pair inside a thread-safe class:
abstract class PairManager {
  Lock lock = new ReentrantLock();
  AtomicInteger checkCounter = new AtomicInteger(0);
  protected Pair p = new Pair();
  private List<Pair> storage =
    Collections.synchronizedList(new ArrayList<Pair>());
  public  Pair getPair() {
    // Make a copy to keep the original safe:
	  lock.lock();
	  try{
		  return new Pair(p.getX(), p.getY());
	  } finally{
		  lock.unlock();
	  }
  }
  // Assume this is a time consuming operation
  protected void store(Pair p) {
    storage.add(p);
    try {
      TimeUnit.MILLISECONDS.sleep(50);
    } catch(InterruptedException ignore) {}
  }
  public abstract void increment();
}


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 exec = Executors.newCachedThreadPool();
    PairManipulator
      pm1 = new PairManipulator(pman1),
      pm2 = new PairManipulator(pman2);
    PairChecker
      pcheck1 = new PairChecker(pman1),
      pcheck2 = new PairChecker(pman2);
    exec.execute(pm1);
    exec.execute(pm2);
    exec.execute(pcheck1);
    exec.execute(pcheck2);
    try {
      TimeUnit.MILLISECONDS.sleep(500);
    } catch(InterruptedException e) {
      System.out.println("Sleep interrupted");
    }
    System.out.println("pm1: " + pm1 + "\npm2: " + pm2);
    System.exit(0);
  }  
}


// Synchronize the entire method:
class ExplicitPairManager1 extends PairManager {
  @Override
public void increment() {//你可以在方法中尝试加synchronized并把下面的lock代码去掉,获取到的x和y就不一定相等了
    lock.lock();
    try {
	  p.incrementX();
      p.incrementY();
      store(getPair());
	} finally {
	  lock.unlock();
	}
  }
}

// Use a critical section:
class ExplicitPairManager2 extends PairManager {
	 
  @Override
public void increment() {
    Pair temp = null;
    lock.lock();
    	try {
  	      p.incrementX();
  	      p.incrementY();
  	      temp = getPair();
  	    } finally {
  	      lock.unlock();
  	    }
    	
    store(temp);
  }
}



public class ExplicitCriticalSection {
  public static void main(String[] args) throws Exception {
    PairManager
      pman1 = new ExplicitPairManager1(),
      pman2 = new ExplicitPairManager2();
    CriticalSection.testApproaches(pman1, pman2);
  }
}

运行结果

pm1: Pair: x: 11, y: 11 checkCounter = 415
pm2: Pair: x: 11, y: 11 checkCounter = 12582011

getPair()和increment()中的lock对象需要是同一个,否则可能造成x增加后,另一个线程读取x和y的值,而此时y的值并没有改变,导致x和y的值不一致。

在方法上使用Synchronized或者同步控制块或者Lock对象,其本质都是获取 一个 对象的锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值