关于java多线程的一些问题,

时间:2011-07-22 11:42:56

标签: java multithreading concurrency

我有一些关于Java多线程问题的问题。请尽可能多地为我提供帮助。

0)假设我们有2个银行账户,我们需要以线程安全的方式在它们之间转账。 即

accountA.money += transferSum; 
accountB.money -= transferSum; 

存在两个要求:

  1. 没有人能够看到操作的中间结果(即一个总和增加,但其他人没有减少)
  2. 在操作过程中不应阻止读取访问(即在操作过程中应显示旧的帐户总和值)
  3. 你能就此提出一些想法吗?

    1)假设2个线程通过synchronized方法或使用显式锁来修改某些类字段。无论同步如何,都不能保证线程可以看到该字段,通过NOT synchronized方法读取它。 - 是否正确?

    2)通知方法唤醒的线程可以等待锁定多长时间?假设我们有这样的代码:

    synchronized(lock) {  
        lock.notifyall();   
        //do some very-very long activity  
        lock.wait() //or the end of synchronized block  
    }  
    

    我们能否说明至少有一个线程会成功并获取锁定?由于某些超时,信号会丢失吗?

    3)来自Java Concurrency Book的引文:

    “单线程执行程序还提供足够的内部同步,以保证任务所做的任何内存写入对后续任务都是可见的;这意味着可以将对象安全地限制在”任务线程“中,即使该线程可能被替换为另一个不时。“

    这是否意味着在单线程执行程序中执行代码的唯一线程安全问题是数据争用,我们可以放弃volatile变量并忽略所有可见性问题?它看起来像是解决大部分并发问题的通用方法。

    4)所有标准的getter和setter都是原子的。如果字段标记为volatile,则无需同步它们。 - 是否正确?

    5)静态字段和静态块的启动由一个线程完成,因此无需同步。 - 是否正确?

    6)为什么一个线程在使用wait()方法离开锁定时需要通知其他人,但是如果它通过退出synchronized块而离开锁定则不需要这样做?

4 个答案:

答案 0 :(得分:7)

0:你不能。

确保原子更新很容易:您可以同步保存银行帐户的任何对象。但是你要么阻止所有读者(因为它们同步),要么你不能保证读者会看到什么。

但是,在诸如银行系统之类的大型系统中,锁定频繁访问的对象是一个坏主意,因为它会引入等待系统。在更改两个值的特定情况下,这可能不是问题:它会发生得如此之快,以至于大多数访问都是无争用的。

当然有办法避免这种竞争条件。数据库为ba nk帐户做得非常好(尽管最终它们依赖于对事务结束的争用访问)。

1)据我所知,除了synchronizedvolatile建立的保证外,没有其他保证。如果一个线程进行同步访问而一个线程没有,则非同步访问没有内存屏障。 (如果我错了,我确定我会被纠正或至少被贬低)

2)引用JavaDoc:"唤醒的线程将无法继续,直到当前线程放弃对该对象的锁定。"如果您决定将睡眠放入同步块中,您将会感到不快。

3)我必须多次阅读这句话才能确定,但​​我相信"单线程执行者"是关键词。如果执行程序仅运行单个线程,则该线程上的所有操作都存在严格的匹配前关系。 意味着在其他执行程序中运行的其他线程可以忽略同步。

4)否longdouble不是原子的(参见JVM spec)。如果要对成员变量进行非同步访问,请使用AtomicXXX对象。

5)否。我无法在JVM规范中找到确切的引用,但第2.17.5节暗示多个线程可能会初始化类。

6)因为所有线程都要等到一个线程进行通知。如果您处于同步块中,并且等待并且没有通知,则每个线程都将等待一个永远不会发生的通知。

答案 1 :(得分:1)

0)这是一个难题,因为您不希望中间结果可见或在操作过程中锁定读者。说实话,我不确定它是否可能,为了确保没有线程看到中间结果,您需要在执行两次写入时阻止读者。

如果您不希望中间结果可见,那么您必须在写作之前锁定两个后退帐户。执行此操作的最佳方法是确保每次都以相同的顺序获取和释放锁(否则会导致死锁)。例如。首先锁定较低的帐号,然后锁定较大的帐号。

1)正确,所有访问必须通过锁定/同步或使用volatile。

2)永远

3)使用单线程执行程序意味着只要所有访问都由该执行程序运行的任务执行,您就不必担心线程安全/可见性。

4)不确定标准getter和setter的含义,但写入大多数变量类型(double和long除外)都是原子的,所以不需要同步,只是为了可见性而易失。请尝试使用Atomic变体。

5)不,两个线程可以尝试使用init一些静态代码,使得Singleton的初始实现不安全。

6)同步和等待/通知是两种不同但相关的机制。没有等待/通知你必须在对象上旋转锁定(即继续获得锁定和轮询)以获得更新

答案 2 :(得分:1)

5)静态字段和静态块的启动由一个线程完成,因此不需要同步。 - 这是对的吗?

VM在同步(clazz)块中执行静态初始化。

static class Foo {
    static {
        assert Thread.holdsLock(Foo.class); // true

        synchronized(Foo.class){  // redundant, already under the lock
            ....

答案 3 :(得分:0)

0)我可以看到这样做的唯一方法是将accountA和accountB存储在AtomicReference中存储的对象中。然后,您可以复制该对象,对其进行修改,如果它仍与原始引用相同,则更新该引用。

AtomicReference<Accounts> accountRef;

Accounts origRef;
Accounts newRef;
do {
   origRef = accountRef.get();
   // make a deep copy of origRef

   newRef.accountA.money += transferSum; 
   newRef.accountB.money -= transferSum; 
} while(accountRef.compareAndSet(origRef, newRef);