📜  门| GATE CS 2012 |第57章(1)

📅  最后修改于: 2023-12-03 15:42:11.576000             🧑  作者: Mango

门 | GATE CS 2012 |第57章

题目链接:https://gateoverflow.in/463/gate2012-57

本题属于GATE 2012计算机科学考试中的第57章,考查了对并发编程中的同步和互斥机制的理解和应用。该题目需要程序员理解和分析多线程程序,并找出其中的错误并进行修复。

题目描述

下面是一个 Java 类的代码片段,它实现了一个2阶段交换协议。模拟了两个进程A和B之间的并发执行。请在保持协议的完整性不变的前提下修复错误。

import java.util.concurrent.locks.*;

class Exchange {
    Object item = null;
    final Lock lock = new ReentrantLock();
    final Condition condition = lock.newCondition();

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (item != null)
                condition.await();
            item = x;
            System.out.println("A puts " + x);
            condition.signal();
        } finally {
            lock.unlock();
        }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (item == null)
                condition.await();
            Object x = item;
            item = null;
            System.out.println("B takes " + x);
            condition.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}

class Producer implements Runnable {
    Exchange ex;

    Producer(Exchange e) {
        ex = e;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                ex.put(i);
            } catch (InterruptedException e) {
            }
        }
    }
}

class Consumer implements Runnable {
    Exchange ex;

    Consumer(Exchange e) {
        ex = e;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Object item = ex.take();
            } catch (InterruptedException e) {
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Exchange ex = new Exchange();
        (new Thread(new Producer(ex))).start();
        (new Thread(new Consumer(ex))).start();
    }
}
题目分析

答案将在下文中给出,首先我们来分析一下代码的实现逻辑。

  • Exchange类定义了一个互斥锁Lock和一个与之配合使用的条件变量Condition;
  • put方法和take方法中通过调用相应的Lock和Condition方法实现互斥和同步;
  • Producer和Consumer类继承了Runnable接口,并在run方法中通过调用Exchange类的put和take方法实现数据的生产和消费;
  • Main类通过创建Producer和Consumer类的实例,并启动两个线程执行run方法实现了数据的并发生产和消费。

由题目描述来看,取数和放数的交替方法看起来是正确的,并且在数据交换之前检查item的值是否为null也符合要求。因此这似乎没有什么问题。但是,如果你运行这段代码会发现它会进入死锁状态。这是为什么呢?

代码分析

首先,在put方法的while循环中防止了过早或过晚的唤醒线程。如果其他线程在执行时错过了signal的调用,就会一直等待,这是一个很好的做法。接着,我们来看一下问题出在哪里。

在take方法中,被放置的值存在item变量中。当该值被从这个变量中移除时,它被设置为null。但是,在之后的signal调用之前,上面的if语句被执行。在執行condition.await()時,线程被阻止,无法在之后的signal调用中接收这个信号(即,线程不是在condition.await语句被执行后再排队等待signal,而是在之前)。

当没有其他可运行的线程时,代码会进入死锁状态。因此,必须将它放到循环中,直到新的值实际被放置才释放锁并离开循环。

修复问题

因此,问题的解决方法就是将条件变量在等待之前放入while循环中。修正的代码如下所示:

public void put(Object x) throws InterruptedException {
    lock.lock();
    try {
        while (item != null) {
            condition.await();
        }
        item = x;
        System.out.println("A puts " + x);
        condition.signalAll();
    } finally {
        lock.unlock();
    }
}

public Object take() throws InterruptedException {
    lock.lock();
    try {
        while (item == null) {
            condition.await();
        }
        Object x = item;
        item = null;
        System.out.println("B takes " + x);
        condition.signalAll();
        return x;
    } finally {
        lock.unlock();
    }
}
参考文献