关键部分 - 思考Java示例

时间:2017-10-28 22:54:56

标签: java multithreading concurrency

我现在阅读书籍Thinking in Java,关于关键部分的章节,我无法理解一个例子,因为我得到了书中没有描述的例外。示例如下所示:

class Pair {
    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 class PairValuesNotEqualException extends RuntimeException {
        public PairValuesNotEqualException() {
            super("Values are not equal: " + Pair.this);
        }
    }

    public void checkState() {
        if (x != y) {
            throw new PairValuesNotEqualException();
        }
    }
}

abstract class PairManager {
    AtomicInteger checkCounter = new AtomicInteger(0);
    protected Pair p = new Pair();

    public synchronized Pair getPair() {
        // Make copies to protect the original
        return new Pair(p.getX(), p.getY());
    }

    public abstract void increment();
}

// synchronization of the whole method
class PairManager1 extends PairManager {

    @Override
    public synchronized void increment() {
        p.incrementX();
        p.incrementY();
    }
}

// Critical section
class PairManager2 extends PairManager {

    @Override
    public void increment() {
        synchronized (this) {
            p.incrementX();
            p.incrementY();
        }
    }
}

class PairManipulator implements Runnable {

    private PairManager pairManager;

    public PairManipulator(PairManager pairManager) {
        this.pairManager = pairManager;
    }

    @Override
    public void run() {
        while (true)
            pairManager.increment();
    }

}

class PairChecker implements Runnable {

    private PairManager pairManager;

    public PairChecker(PairManager pairManager) {
        this.pairManager = pairManager;
    }

    @Override
    public void run() {
        while (true) {
            pairManager.checkCounter.incrementAndGet();
            pairManager.getPair().checkState();
        }
    }
}

public class CriticalSection {

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

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

示例输出:

pm1: Pair: Pair{x=364, y=364} counter = 471421
pm2: Pair: Pair{x=365, y=365} counter = 1015604598

这个例子毫无例外地执行。

在上面的示例中,我了解它是如何工作的,但问题出在显式锁的示例中。 来自book的显式锁定示例:

class ExplicitPairManager1 extends PairManager {
    private Lock lock = new ReentrantLock();
    // why synchronized ??
    public synchronized void increment() {
        lock.lock();
        try {
            p.incrementX();
            p.incrementY();
        } finally {
            lock.unlock();
        }
    }
}

class ExplicitPairManager2 extends PairManager {
    private Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            p.incrementX();
            p.incrementY();
        } finally {
            lock.unlock();
        }
    }
}

public class ExplicitCriticalSection {

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

输出:

Exception in thread "pool-1-thread-4" critical.sections.Pair$PairValuesNotEqualException: Values are not equal: Pair{x=2, y=1}
    at critical.sections.Pair.checkState(CriticalSection.java:49)
    at critical.sections.PairChecker.run(CriticalSection.java:133)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
pm1: Pair: Pair{x=1024, y=1024} counter = 3
pm2: Pair: Pair{x=1025, y=1025} counter = 1499445

首先,我不明白为什么作者在ExplicitPairManager1#increment中使用synchronized,如果他也使用Lock对象?这是书中的错误吗?

第二个问题是我不明白为什么我会例外?

引入了豁免:

class PairChecker implements Runnable {

    private PairManager pairManager;

    public PairChecker(PairManager pairManager) {
        this.pairManager = pairManager;
    }

    @Override
    public void run() {
        while (true) {
            pairManager.checkCounter.incrementAndGet();
            pairManager.getPair().checkState();  // here was thrown an exception
        }
    }
}

为什么我得到了饶恕和作者不?可能的JVM行为在不同的系统上是不同的吗?我使用Ubuntu 16.04 LTS和Java 8。

1 个答案:

答案 0 :(得分:0)

如果要为多个线程建立临界区,则需要在同一对象上进行同步。

ExplicitPairManager2修改了对的异常。

让我们看看异常导致流程的可能性如何:

    获取
  1. ExplicitPairManager2.lock.lock()
  2. ExplicitPairManager2.p.incrementX()发生了
  3. PairChecker调用getPair()
  4. PairChecker获取pairManager的内部(this)监视器,但它与ExplicitPairManager2.lock不同
  5. getPair()的结果因此有x!= y
  6. 所以最后没有关键部分。

    换句话说,在修改时,您使用两个不同的对象进行同步:

    • ExplicitPairManager2.lock撰写
    • ExplicitPairManager2this)的内部监视器,用于创建用于检查状态的副本