java - 读者/作家通过引用

标签 java

以下代码是Java中的Passing the Baton程序的一部分:

主要
P1(作家)
P2(作家)
P3(阅读器)
P4(阅读器)

主要();

package ReadersPreference;

import java.util.concurrent.Semaphore;

/**
  * * @author me
 */
public class Main {

public static void main(String[] args) {


    AnyData x = new AnyData(5.7);//gives writers something to write, 
                                   //readers something to read
    Semaphore e = new Semaphore(1);//control entry
    Semaphore r = new Semaphore(0);//used to delay readers
    Semaphore w = new Semaphore(0);//used to delay writers

    int nr = 0;//readers active
    int nw = 0;//writers active

    int dr = 0;//readers waiting
    int dw = 0;//writers waiting

    P1 r1 = new P1(e, r, w, x, nr, nw, dr, dw); // #reader thread 1
    P2 r2 = new P2(e, r, w, x, nr, nw, dr, dw); // #reader thread 2
    P5 r3 = new P5(e, r, w, x, nr, nw, dr, dw); // #reader thread 3
    P6 r4 = new P6(e, r, w, x, nr, nw, dr, dw); // #reader thread 4

    P3 w1 = new P3(e, r, w, x, nr, nw, dr, dw); // #writer thread 1
    P4 w2 = new P4(e, r, w, x, nr, nw, dr, dw); // #writer thread 2

System.out.println("threads commanded to start");

r1.start();     // calls run() method in Thread
r2.start();
r3.start();     
r4.start();   
w1.start();
w2.start();
}//end of main
}//end of class

读者流程
package ReadersPreference;

import java.util.concurrent.Semaphore;

public class P1 extends Thread {

private Semaphore e;
private Semaphore r;
private Semaphore w;
private AnyData pillarbox;
private int nw;
private int nr;
private int dr;
private int dw;

public P1(Semaphore e, Semaphore r, Semaphore w, AnyData pbox,
        int nw, int nr, int dr, int dw) {

    this.nw = nw;
    this.nr = nr;
    this.dr = dr;
    this.dw = dw;

    this.e = e;
    this.r = r;
    this.w = w;
    pillarbox = pbox;
}// end of constructor

public void run() {

PERFORM OPERATIONS          

}// end of run method
}// end of class

现在,根据我的输出,它似乎可以工作。但是,我的讲师指出了两个主要缺陷。一种是计数器(nr,nw,dr,dw)是通过值而不是通过引用传递的。这意味着每个线程都会检查自己的数据副本,而不是使用共享变量,这会阻止程序按其应有的方式运行。

我一直在阅读有关通过价值和参考传递的内容,起初我感到头肿胀,我想我现在已经了解了大部分内容,但是我仍然没有找到解决方案。我可以理解为什么信号量已经工作,因为它们已经是一个引用(而传递的值是一个引用)。我看不出计数器到底在哪里或如何解决它。

当他说线程时,他是指流程类(构造函数/算法)还是主类中的实例化或两者?

基于阅读作为对象的共享原语,最接近解决方案的是:
public void setCounters(int nr){nr = newNR;}   

再次,尽管我对如何实现它含糊不清

第二个主要的缺陷是我创建了多个进程类文件,并将每个文件作为线程运行,而不是编写两个(读取器/写入器)并根据需要使用了许多对象。我不明白这是什么意思,但是,考虑到我的输出打印语句对于每个过程都是唯一的,因此要在输出中唯一地标识每个输出以进行验证,为什么将这种方法用于某个目的时会认为是缺陷,对读者/作家的解决方案有影响吗?

最佳答案

首先,好的变量名总是比注释更好;遵守这个规则,您就不会出错。想象一下,我在代码中的某个地方遇到了您的e变量,现在我必须滚动到类的顶部,并阅读注释以查看其含义,然后返回到原来的位置。这使得代码几乎不可读...

您的第一个问题是您正在使用的int是原始类型,它是通过值传递的。 @Marcin的解决方案不是线程安全的;如果您执行类似int++的操作,那么当被多个线程调用时,这可能会做各种奇怪的事情(例如不递增,返回错误的值等)。 始终在多线程操作中使用线程安全对象。

正如@Marcin建议的那样,您可以将数据包装在一个类中以减少代码量:

public class SharedData<T> {

    private final T data;
    private final Semaphore entryControl = new Semaphore(1);
    private final Semaphore readerDelay = new Semaphore(0);
    private final Semaphore writerDelay = new Semaphore(0);
    private final AtomicInteger activeReaders = new AtomicInteger(0);
    private final AtomicInteger activeWriters = new AtomicInteger(0);
    private final AtomicInteger waitingReaders = new AtomicInteger(0);
    private final AtomicInteger waitingWriters = new AtomicInteger(0);

    public SharedData(final T data) {
        this.data = data;
    }
    //getters
}

我已经将此类设置为泛型类,但是您可以根据需要删除泛型并将“数据”作为“对象”-泛型更好,因为它提供了类型安全性,请阅读here

该类使用AtomicInteger对象,这是一个线程安全的整数,允许进行诸如getAndSet(int newValue)之类的原子操作-这样就消除了访问单个值时出现线程安全问题的可能性,但看起来您可能想要访问两个值,但这仍然不是线程安全的因此,您可能需要按照以下步骤向数据类添加一些方法:
public synchronized void makeReaderActive() {
    //perform checks etc
    waitingReaders.decrementAndGet();
    activeReaders.incrementAndGet();
}

否则,读者可能会递减waitingReaders,然后在递增之前先读取activeReaders

我认为,这可以解决您的第一个查询;现在到您的第二个。老师说您创建了多个流程类文件,而不是创建实例。这是因为您复制粘贴的同一文件后会调用不同的内容(P1P2等),这不是Java的工作方式。考虑在创建Semaphore的地方的代码:
Semaphore e = new Semaphore(1);//control entry
Semaphore r = new Semaphore(0);//used to delay readers

您有一个类文件(Semaphore.class),并已创建了两个实例。您不必将JDK的Semaphore类复制到另一个文件中并创建该文件。您不必这样做:
Semaphore1 e = new Semaphore1(1);//control entry
Semaphore2 r = new Semaphore2(0);//used to delay readers

所以;在您的示例中,我假设您有一些Reader进程和一些Writer进程,然后需要两个类:
public class MyReader implements Callable<Void> {

    private final String name;
    private final SharedData sharedData;

    public MyReader(final String name, final SharedData sharedData) {
        this.name = name;
        this.sharedData = sharedData;
    }

    @Override
    public Void call() {
        //do stuff
        return null;
    }
}

public class MyWriter implements Callable<Void> {

    private final String name;
    private final SharedData sharedData;

    public MyWriter(final String name, final SharedData sharedData) {
        this.name = name;
        this.sharedData = sharedData;
    }

    @Override
    public Void call() {
        //do stuff
        return null;
    }
}

一类代表您所有作家的所作所为,另一类代表您所有读者的所作所为。我使它们成为Callable而不是线程;这使我想到了下一个要点。

您不应该使用Thread对象,这些对象级别很低并且很难正确管理。您应该使用新的ExecutorService。因此,您的main方法现在看起来像:
public static void main(String[] args) {
    final SharedData<Double> sharedData = new SharedData<Double>(5.7);
    final List<Callable<Void>> myCallables = new LinkedList<Callable<Void>>();

    for (int i = 0; i < 4; ++i) {
        myCallables.add(new MyReader("reader" + i, sharedData));
    }
    for (int i = 0; i < 2; ++i) {
        myCallables.add(new MyWriter("writer" + i, sharedData));
    }

    final ExecutorService executorService = Executors.newFixedThreadPool(myCallables.size());
    final List<Future<Void>> futures;
    try {
        futures = executorService.invokeAll(myCallables);
    } catch (InterruptedException ex) {
        throw new RuntimeException(ex);
    }
    for (final Future<Void> future : futures) {
        try {
            future.get();
        } catch (InterruptedException ex) {
            throw new RuntimeException(ex);
        } catch (ExecutionException ex) {
            throw new RuntimeException(ex);
        }
    }
}

让我带您看一下这段代码,以便您了解此处的情况。在第一行,我们创建一个新的SharedData类-在这种情况下,它是SharedData<Double>;这意味着它包含的dataDouble类型;这可能是任何东西。

接下来,我们创建一个ListCallable,这是我们的工作 class 去的地方。然后,我们将工作类循环放入List中。请注意,我们为每个MyReaderMyWriter创建了相同类的实例-我们不需要每个类都有一个类文件。

然后,我们创建一个新的ExecutorService,其线程池的大小与我们创建的工作类的数量相同-注意,我们可以拥有更少的线程。在这种情况下,每个线程将分配一个工作类,然后在完成工作后将为其分配一个新的工作类,直到完成所有工作。在我们的例子中,有足够的线程,因此每个简单的线程都会分配一个工作类。

现在,我们在工作类的invokeAll中传递List,这是我们要求ExecutorService对所有call进行Callable的地方。该方法一直阻塞,直到一切都完成为止,它可能会像其他任何等待方法一样抛出InterrupedException-在这种情况下,我们抛出异常并退出。

最后,这就是ExecutorService的亮点,我们遍历返回的Future类列表并调用get-如果在与该将来相关的工作中遇到任何问题,这将抛出ExecutionException。在这种情况下,我们抛出异常。

注意Callable的类型为Void(即声明为Callable<Void>),然后过滤器过滤到Future的类型为Future<Void>。如果要从每个进程返回一些数据,则可以将类型更改为Callable<MyData>,然后getFuture方法将向您返回此数据。

关于java - 读者/作家通过引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15254737/

相关文章:

java - 如何根据登录状态从Button切换到FrameLayout

java - 面临为 Eclipse Juno 安装 m2e maven 插件的问题

java - 仅当按下按钮时才在 JTable 中保存单元格更改,如果不像以前那样保留

java - 如何限制为 double 打印的小数位数?

java - 如何在 Jersey 中传递元素列表作为参数?

java - 如何在 Eclipse IDE 中执行 shell 脚本?

java - 如何通过方向更改来维护过滤的 CursorAdapter 的状态?

java - 未调用 join 后的 Spring Statemachine 内部转换操作

java - JLabel html 文本忽略 setFont

java - JAXB:编码后替换 namespace