【问题标题】:Thread safe read/write to a counter线程安全读/写计数器
【发布时间】:2026-01-15 09:40:02
【问题描述】:

我正在尝试使用线程安全方法创建 2 个线程来读取/写入计数器。

我已经编写了一些代码来尝试对此进行测试,但读取线程只是在其最大值 (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());
    }
}
}

使用 Atomic Integer 来保存计数器的值以允许我安全地递增并获取值会更好吗?

【问题讨论】:

  • 是的,但那是因为您当前的解决方案使用方法级同步。如果一个线程当前正在访问increment,则不会阻止另一个线程同时访问getVal。另一种解决方案可能是将两个方法体包装在 synchronized(this) {} 块中
  • @JeroenSteenbeeke 另一个线程将在任何synchronized 代码块上被阻塞,因为该线程不会是对象隐式锁的所有者。
  • 对不起,Read 只是读取 MAX 值这一事实是否意味着线程 已同步?当 Increment 正在访问 c 时,没有其他线程可以访问它,因此 Read 线程只有在 Increment 完成写入时才读取 c。
  • 尝试将您的 MAX 提高到一些实质性的东西 (1_000_000_000),您应该会看到一些线程交错。数到 1000 并不费力。如果您希望读者在写入(递增)内容后立即阅读,您可以添加 wait()notify(),是的,使用 AtomicInteger 是一个更好的解决方案。
  • "在同一个对象上两次调用同步方法是不可能交错的。当一个线程为一个对象执行同步方法时,所有其他线程调用该对象的同步方法相同的对象块(暂停执行),直到第一个线程处理完该对象。" docs.oracle.com/javase/tutorial/essential/concurrency/…

标签: java multithreading thread-safety atomic


【解决方案1】:

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

【讨论】:

  • 没错。尝试在 Counter.increment() 中添加延迟,您会看到在写入计数器时没有人会读取。
【解决方案2】:

如果您希望更频繁地交错执行 Read 线程和 Increment 线程,然后自然操作系统线程抢占,只需让每个线程放弃它们的锁(通过在各自的run() 方法中调用&lt;lockedObject&gt;.wait() 后跟&lt;lockedObject&gt;.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 亿)使胎面时不时地交错(在我的机器上,交错只是通过观察 150 和 400_000 次迭代)。

【讨论】:

  • @DavidSchwartz ReadIncrement 是独立的,不会停顿。每个主循环在没有另一个的情况下进展到 MAX。您完全删除任何读取线程,增量将计为 MAX。您可以为每次迭代设置 Read sleep() 使其读取非连续增量。请(重新)测试。