19

在什么情况下需要同步对实例成员的访问?我知道对类的静态成员的访问总是需要同步的——因为它们在类的所有对象实例之间共享。

我的问题是,如果我不同步实例成员,我什么时候会出错?

例如,如果我的班级是

public class MyClass {
    private int instanceVar = 0;

    public setInstanceVar()
    {
        instanceVar++;
    }

    public getInstanceVar()
    {
        return instanceVar;
    }
}

在什么情况下(使用类MyClass)我需要方法: public synchronized setInstanceVar()public synchronized getInstanceVar()

提前感谢您的回答。

4

6 回答 6

39

synchronized修饰符确实是一个主意,应该不惜一切代价避免。我认为 Sun 试图让锁定更容易实现是值得称道的,但这synchronized只会带来更多的麻烦,而不是值得。

问题是一个synchronized方法实际上只是获取锁this并在方法执行期间持有它的语法糖。因此,public synchronized void setInstanceVar()将相当于这样的事情:

public void setInstanceVar() {
    synchronized(this) {
        instanceVar++;
    }
}

这很糟糕,原因有两个:

  • 同一类中的所有synchronized方法都使用完全相同的锁,这会降低吞吐量
  • 任何人都可以使用锁,包括其他班级的成员。

没有什么可以阻止我在另一个班级做这样的事情:

MyClass c = new MyClass();
synchronized(c) {
    ...
}

在该synchronized块中,我持有所有synchronized方法都需要的锁MyClass。这进一步降低了吞吐量并显着增加了死锁的机会。

更好的方法是拥有一个专用lock对象并直接使用该synchronized(...)块:

public class MyClass {
    private int instanceVar;
    private final Object lock = new Object();     // must be final!

    public void setInstanceVar() {
        synchronized(lock) {
            instanceVar++;
        }
    }
}

或者,您可以使用java.util.concurrent.Lock接口和java.util.concurrent.locks.ReentrantLock实现来获得基本相同的结果(实际上,在 Java 6 上也是如此)。

于 2008-11-21T18:18:50.150 回答
20

这取决于您是否希望您的类是线程安全的。大多数类不应该是线程安全的(为简单起见),在这种情况下您不需要同步。如果您需要它是线程安全的,您应该同步访问使变量可变。(它避免了其他线程获取“陈旧”数据。)

于 2008-11-21T18:01:27.690 回答
3

如果你想让这个类线程安全,我会声明instanceVarvolatile确保你总是从内存中获得最新的值,而且我也会这样做,setInstanceVar() synchronized因为在 JVM 中,增量不是原子操作。

private volatile int instanceVar =0;

public synchronized setInstanceVar() { instanceVar++;

}
于 2008-11-21T18:08:27.940 回答
1

. 粗略地说,答案是“视情况而定”。在此处同步您的 setter 和 getter 仅具有保证多个线程无法读取彼此之间的变量增量操作的预期目的:

 synchronized increment()
 { 
       i++
 }

 synchronized get()
 {
   return i;
  }

但这甚至在这里都行不通,因为要确保您的调用者线程获得与它增加的相同的值,您必须保证您是原子地增加然后检索,而您在这里没有这样做 - 即您必须做类似的事情

  synchronized int {
    increment
    return get()
  }

基本上,同步对于定义需要保证运行线程安全的操作很有用(换句话说,您不能创建一个单独的线程破坏您的操作并使您的类行为不合逻辑或破坏您期望的数据状态的情况成为)。这实际上是一个比这里可以解决的更大的话题。

这本书Java Concurrency in Practice非常好,而且肯定比我可靠得多。

于 2008-11-21T18:15:10.687 回答
1

简单地说,当您有多个线程访问同一实例的同一方法时,您使用同步,这将改变对象/或应用程序的状态。

它是一种防止线程之间竞争条件的简单方法,实际上你应该只在计划让并发线程访问同一个实例(例如全局对象)时使用它。

现在,当您使用并发线程读取对象实例的状态时,您可能需要查看 java.util.concurrent.locks.ReentrantReadWriteLock ——理论上它允许同时读取多个线程,但仅允许一个线程写入。因此,在每个人似乎都在给出的 getter 和 setting 方法示例中,您可以执行以下操作:

public class MyClass{
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private int myValue = 0;

    public void setValue(){
        rwl.writeLock().lock();
        myValue++;
       rwl.writeLock().unlock();
    }

    public int getValue(){
       rwl.readLock.lock();
       int result = myValue;
       rwl.readLock.unlock();
       return result;
    }
}
于 2008-11-21T18:46:26.240 回答
-3

在 Java 中,对 int 的操作是原子的,所以不,在这种情况下,如果您所做的只是一次 1 次写入和 1 次读取,则不需要同步。

如果这些是 long 或 double,您确实需要同步,因为可能会更新 long/double 的一部分,然后读取另一个线程,最后更新 long/double 的另一部分。

于 2008-11-21T18:04:32.363 回答