【问题标题】:Java share a variable between two threadsJava在两个线程之间共享一个变量
【发布时间】:2010-08-16 13:49:58
【问题描述】:

我有两个线程。调用修改变量的类的更新方法。另一个调用读取变量的类的更新方法。只有一个线程写入,一个(或多个)线程读取该变量。由于我是多线程新手,在并发方面我需要做什么?

public class A
{
    public int variable; // Does this need to be volatile?
       // Not only int, could also be boolean or float.
    public void update()
    {
        // Called by one thread constantly
        ++variable;
        // Or some other algorithm
        variable = complexAlgorithm();
    }
}

public class B
{
    public A a;
    public void update()
    {
        // Called by another thread constantly
        // I don't care about missing an update
        int v = a.variable;
        // Do algorithm with v...
    }
}

谢谢,

【问题讨论】:

  • 下面的许多答案都假设您正在执行可以由 AtomicInteger 类处理的整数操作。对于更复杂的内容,请查看 synchronized 块或 java.util.conccurent.locks.Lock

标签: java multithreading concurrency


【解决方案1】:

如果只有一个线程写入variable,您可以将其设为volatile。否则请参阅AtomicInteger 的答案。

只有volatile 会在只有一个写入线程的情况下工作,因为只有一个写入线程,所以它始终具有正确的variable 值。

【讨论】:

  • 那是我的观点。我知道只有一个线程修改它,但可能有很多线程在读取它。
  • @Dave - 好吧,这就是我发布这个答案的原因。如果您的多线程很简单,只有一个线程写入并且您只有一个变量可以写入供其他线程读取,那么您不需要复杂的解决方案,volatile int 变量就可以了。在一般情况下,您可能需要锁、同步或 AtomicInteger。但是您的问题非常具体,涉及编写器线程的数量和所涉及的变量数量。不要被其他答案的投票吓倒。如果我的解决方案适合您的需求,请接受。
  • 非常感谢,我只是需要第二个意见。我也从来不知道并发包中的原子变量和其他好东西,我很高兴这里的人们能够很好地解释。将来它可能对我很有价值。
【解决方案2】:

在这种情况下,我会使用AtomicInteger,但一般的答案是对变量的访问应该受到同步块的保护,或者使用 java.util.concurrent 包的另一部分。

几个例子:

使用同步

public class A {
    public final Object variable;
    public void update() {
        synchronized(variable) {
            variable.complexAlgorithm();
        }
    }
}

public class B {
    public A a;
    public void update() {
        sychronized(a.variable) {
            consume(a.variable);
        }
    }
}

使用 java.util.concurrent

public class A {
    public final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public final Object variable;
    public void update() {
        lock.writeLock().lock();
        try {
            variable.complexAlgorithm();
        } finally {
            lock.writeLock().unlock();
        }
    }
}

public class B {
    public A a;
    public void update() {
        a.lock.readLock().lock();
        try {
            consume(a.variable);
        } finally {
            a.lock.readLock().unlock();
        }
    }
}

【讨论】:

  • 你能解释一下为什么 A 类中没有 variable.getWriteLock().lock() 吗?
  • 您只能在一个对象上同步,并且该对象需要是最终的。在此示例中,同步是通过 ReadWriteLock 的实现完成的(因此多个线程可以同时读取) - 请参阅 docs.oracle.com/javase/6/docs/api/java/util/concurrent/locks/…
【解决方案3】:

不仅variable 应该是volatile,而且你还想用some sort of synchronization 保护你的update 函数,因为++variable 不是原子调用。毕竟,它只是

的语法糖
variable = variable + 1;

这不是原子的。

您还应该将读取变量的所有调用包装在某种lock 中。

或者,使用AtomicInteger。它是为这类事情而设计的(仅用于整数运算)。

public class A
{
    // initially had said volatile wouldn't affect this variable because
    // it is not a primitive, but see correction in comments
    public final AtomicInteger variable; // see comments on this issue of why final
    public void update()
    {
        // Called by one thread constantly
        variable.getAndIncrement(); // atomically adds one
    }
    public int retrieveValue()
    {
        return variable.get(); // gets the current int value safely
    }
}

public class B
{
    public A a;
    public void update()
    {
        // Called by another thread constantly
        int v = a.retrieveValue();
        // Do algorithm with v...
    }
}

对于更复杂的算法,如您最近的编辑所假设的,请使用同步或锁定。

【讨论】:

  • 通过说“某种锁”,你的意思是“把它放在一个同步块中或使方法同步”不是吗?此外,+1 与 AtomicInteger
  • 或者使用某种java.util.concurrent.Lock。我更喜欢 synchronized 以获得更多表现力 - 我特别喜欢 ReadWriteLock
  • 您的评论“易失性不会影响这一点,它不是原始的”具有误导性。它与该字段是否是原始类型无关,而是与 variable 不再被重新分配这一事实有关。建议在这里制作variable final。
  • 我做了一些调整。如果我还需要布尔值和浮点数怎么办。如果我不在乎错过更新怎么办?
  • @Dave - AtomicBoolean:download-llnw.oracle.com/javase/1.5.0/docs/api/java/util/…。没有 AtomicFloat。请考虑使用上面链接的同步和锁定教程来锁定您对这些变量的访问。如果不止一个变量代表您的状态,请锁定所有变量而不是使用原子类 - 那些只能安全地一次访问一个状态对象。
【解决方案4】:

使用AtomicIntegersynchronize 访问是安全的。

【讨论】: