博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

[原创] Thinking in Java P689 同步问题

Posted on 2011-09-20 16:27  |orz  阅读(767)  评论(1编辑  收藏  举报
//: concurrency/CriticalSection.java
// Synchronizing blocks instead of entire methods. Also
// demonstrates protection of a non-thread-safe class
// with a thread-safe one.
//package concurrency;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.*;

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++; }
  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();
}

// Synchronize the entire method:
class PairManager1 extends PairManager {
  public synchronized void increment() {
    p.incrementX();
    p.incrementY();
    store(getPair());
  }
}

// Use a critical section:
class PairManager2 extends PairManager {
  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;
  }
  public void run() {
    while(true)
      pm.increment();
  }
  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;
  }
  public void run() {
    while(true) {
      pm.checkCounter.incrementAndGet();
      pm.getPair().checkState(); //此处可能没有互斥,getPair()使用的是synchronized提供的对象锁,其他锁不起作用
    }
  }
}

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);
  }
  public static void main(String[] args) {
    PairManager
      pman1 = new PairManager1(),
      pman2 = new PairManager2();
    testApproaches(pman1, pman2);
  }
} /* Output: (Sample)
pm1: Pair: x: 15, y: 15 checkCounter = 272565
pm2: Pair: x: 16, y: 16 checkCounter = 3956974
*///:~
//: concurrency/ExplicitCriticalSection.java
// Using explicit Lock objects to create critical sections.
//package concurrency;
import java.util.concurrent.locks.*;

// Synchronize the entire method:
class ExplicitPairManager1 extends PairManager {
  private Lock lock = new ReentrantLock();
  public Pair getPair() { //覆盖基类方法,统一使用Lock锁
    // Make a copy to keep the original safe:
	lock.lock();
	try {
	  return new Pair(p.getX(), p.getY());
	} finally {
		lock.unlock();
	}
  }
  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();
  public void increment() {
    Pair temp;
    lock.lock(); //与对象pm2的getPair()中持有锁的方式不同
    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);
  }
} /* Output: (Sample)
pm1: Pair: x: 15, y: 15 checkCounter = 174035
pm2: Pair: x: 16, y: 16 checkCounter = 2608588
*///:~
明确下synchronized的几个关键点:

A.无论synchronized关键字加在方法上还是对象上,他取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。 
B.每个对象只有一个锁(lock)和之相关联。 
C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。 

synchronized可以加在方法上,也可以加在对象上,通常理解为,只有持有了锁才可以进行对应代码块的执行。

java.util.concurrent.locks包下面提供了一些锁的实现,有读写锁,公平锁等。

将synchronized替换成lock的实现可以提升性能:

1. 大部分应用场景是读写互斥,写和写互斥,读和读不互斥。而synchronized则是都互斥。

    可以利用读写锁来优化性能,读锁锁住读的代码块,写锁锁住写的代码块。

2. 要确保你在理解原来利用到synchronized的代码逻辑,避免一概而论地把synchronized替换成锁。

public void getPair(){   
          return "x="+x + ",y="+y;   
}   
  
  
//这个函数   
public synchronized void  increment() {               
            x++;     
            y++;        
            getPair();   
                                
}
  
//可以替换成   
public void  increment() {   
  lock.lock();   
  try{   
    x++;   
    y++;   
    getPair();   
  }finally{   
   lock.unlock();   
}   
  
//但是,如果getPair()是synchronized    
public synchronized void getPair(){   
          return "x="+x + ",y="+y;   
}   
//还能替换这个函数吗?   
public synchronized void  increment() {               
            x++;     
            y++;               
            getPair();                         
 }     
//这时候就不能简单地使用lock来替换了,这里要调用getPair();必需申请到对象锁,这个时候increment也要竞争这把锁   
//因此这里的代码效果是读写互斥。   
//如果只是用lock来锁住increment,则达不到效果。还得同时锁getPair();   
//这里嵌套了synchronized,而synchronized的嵌套结构中在同一个对象的方法上是共享一把锁的。   
//上面只是简单的例子,java编程思想的多线程编程中有更详细的这个例子,有兴趣的可以看看。   
  
}