Java共享变量在两个线程之间

19

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

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...
    }
}

Thanks,


2
下面的许多答案都假定您正在进行可以由AtomicInteger类处理的整数操作。对于更复杂的操作,请查看synchronized块或java.util.conccurent.locks.Lock - justkt
4个回答

19

如果只有一个线程写入variable,那么您可以使用volatile。否则,请参考使用AtomicInteger的答案。

只有在仅有一个写线程的情况下,volatile才能起作用,因为只有一个写线程,所以它始终具有正确的variable值。


那就是我的观点。我知道只有一个线程在修改它,但可能会有许多线程在读取它。 - Dave
@Dave - 嗯,这就是为什么我发布了这个答案。如果你的多线程很简单,只有一个线程写入,并且你只有一个变量它写入要供其他线程读取,那么你不需要复杂的解决方案,使用volatile int变量即可。在一般情况下,你可能需要锁,同步或AtomicInteger。但是你的问题对编写者线程数和涉及的变量数量非常具体。不要被其他答案的得票率所吓倒。如果我的解决方案符合你的需求,请接受它。 - hidralisk
非常感谢,我只是需要第二个意见。我也从未了解过原子变量和并发包中的其他好处,我很高兴这里的人们足够友善地解释了它们。这在将来可能对我很有价值。 - Dave

9
在这种情况下,我会使用AtomicInteger,然而通用的答案是,变量的访问应该通过synchronized块进行保护,或者使用java.util.concurrent包的另一部分。
以下是一些示例:
使用synchronized:
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()呢? - Radu
你只能在一个对象上进行同步,而且该对象需要是 final 的。在这个例子中,同步是通过 ReadWriteLock 的实现完成的(因此多个线程可以同时读取)- 请参阅 http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html。 - Jon Freedman

8

不仅应该将variable设置为volatile,而且您还需要使用某种同步机制来保护您的update函数,因为++variable不是原子调用。毕竟,它只是语法糖,相当于

variable = variable + 1;

这不是原子操作。

您还应该用某种类型的包装任何读取变量的调用。

或者,使用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...
    }
}

对于更复杂的算法,建议使用同步或锁定技术,如您最近所做的编辑所示。


你所说的“某种锁定”,是指“将其放入同步块中或使方法同步”对吧? 此外,支持 AtomicInteger。 - Riduidel
或者使用java.util.concurrent.Lock。我更喜欢它比synchronized更具表现力 - 我特别喜欢ReadWriteLock - justkt
2
你的评论“volatile不会影响这个,它不是一个原始类型”是误导性的。这与字段是否为原始类型无关,而与variable不再被重新分配有关。在这里建议将variable声明为final。 - Mark Peters
1
如果在单个线程中完成更新并且读取是原子的,则不需要同步更新。 - starblue
这个问题有关决赛的评论在哪里? - ThirtyOneTwentySeven
显示剩余2条评论

5

使用AtomicInteger或通过同步访问来确保安全。


你能解释一下“同步”是什么意思吗? 比如说它应该放在哪里,对我来说有点困惑。 - Dave
@Dave - http://download.oracle.com/javase/tutorial/essential/concurrency/sync.html 这个链接可以帮助你了解基本的Java同步。 - justkt

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