线程安全的计数器读写

3

我正在尝试创建两个线程,使用线程安全的方法读写计数器。

我编写了一些代码来测试这个问题,但是读取线程只能读取到计数器的最大值(1000)。

主要代码:

public static void main(String[] args) {

    Counter c = new Counter();

    Thread inc = new Increment(c);
    Thread read = new Read(c);

    inc.start();
    read.start();

}

计数器:

public class Counter {

private int count;

public Counter() {
    count = 0;
}

public synchronized void increment() {
    count++;
}

public synchronized int getVal() {
    return count;
}

}

增量:

public class Increment extends Thread {

private static final int MAX = 1000;
private Counter myCounter;

public Increment(Counter c) {
    myCounter = c;
}

public void run() {
    for (int i = 0; i < MAX; i++) {
        myCounter.increment();
    }
}
}

阅读:

public class Read extends Thread {

private static final int MAX = 1000;
private Counter myCounter;

public Read(Counter c) {
    myCounter = c;
}

public void run() {
    for (int i = 0; i < MAX; i++) {
        System.out.println(myCounter.getVal());
    }
}
}

我是否最好使用原子整数来保存计数器的值,以便安全地增加它并获取该值?


1
是的,但这是因为您当前的解决方案使用了方法级别的同步。如果一个线程正在访问“increment”,那么另一个线程在同一时间内访问“getVal”时不会被阻塞。另一种解决方案可以是将两个方法体都包装在“synchronized(this){}”块中。 - Jeroen Steenbeeke
@JeroenSteenbeeke,任何synchronized代码块都会阻塞另一个线程,因为该线程不是对象的隐式锁的所有者。 - diginoise
1
请原谅,但是Read函数仅读取MAX值的事实是否意味着线程已被同步?当Increment函数访问c时,其他线程无法访问它,因此Read线程只在Increment写完c后才读取c。 - Luke
1
尝试将您的MAX增加到实质性的值(1_000_000_000),您应该会看到一些线程交错。数到1000并不需要努力。如果您希望读者在写入(递增)后立即读取,可以添加wait()notify(),而使用AtomicInteger是一个更好的解决方案。 - diginoise
当同一对象的两个同步方法被调用时,它们不可能交错执行。当一个线程正在执行对象的同步方法时,所有其他调用该对象同步方法的线程会被阻塞(挂起执行),直到第一个线程完成该对象为止。 - Luke
显示剩余3条评论
2个回答

3

您的代码完全没有问题。只是恰巧增量线程在读取线程有机会读取之前完成了所有增量。 1000次增量几乎不需要任何时间。


没错。试着在Counter.increment()中添加一个延迟,你会发现当计数器正在被写入时,没有人会读取它。 - Luke

0

如果想要更频繁地交替执行“读取”线程和“增加”线程,而不是自然的操作系统线程抢占,只需让每个线程放弃其锁定(通过调用相应的run()方法中的<lockedObject>.wait()后跟<lockedObject>.notify()notifyAll()):

[在读取器中]:

public void run() {
    for (int i = 0; i < MAX; i++) {
        synchronized (myCounter) {
            System.out.println(myCounter.getVal());
            try {
                myCounter.wait(0L, 1);
                myCounter.notifyAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

[在增量中]:

public void run() {
    for (int i = 0; i < MAX; i++) {
        synchronized (myCounter) {
            myCounter.increment();
            try {
                myCounter.wait(0L, 1);
                myCounter.notifyAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

MAX常量提高到1_000_000_000(10亿)会使线程不时地交错执行(在我的机器上,交错发生在150400_000次迭代之间的几个打印输出之间)。

@DavidSchwartz ReadIncrement 是独立的,不会相互阻塞。每个主循环都可以在没有另一个循环的情况下进展到 MAX。如果您完全删除任何 Read 线程,Increment 将计数到 MAX。您可以让 Read 在每次迭代中 sleep() 一段时间,这将使其读取非连续的增量。请重新测试。 - diginoise

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接