Java在对象上同步

6
如何同步来自同一个类的两种不同方法以锁定同一对象?以下是一个例子:
public class MyClass extends Thread implements Observer{
  public List<AnotherClass> myList = null;
  
  public MyClass(List<AnotherClass> myList){
    this.myList = myList;
  }
  
  public void run(){
    while(true){
       //Do some stuff 
       myList.add(NotImportantElement);
    }
  }

  public void doJob{
    for(int i=0; i<myList.size; i++){
      ElementClass x = myList.get(i);
      //Do some more stuff
    }
  }
}

问题在于如何防止run()在执行doJob时访问myList,反之亦然?
想象一下:我启动线程并开始向列表中添加元素。在随机的某个时刻,我从另一个持有对我的线程引用的类中调用doJob()。
如何进行锁定?谢谢!
L.E.
好的,我理解了锁的概念,但现在我有另一个问题。
假设我有一个带有public static myList的类,并且该类只有一个实例。我从该实例创建n个Thread实例,每个实例都使用该列表的每个元素并进行一些操作。
现在,在特定时刻,myList已更新。那些已经处理myList元素的线程会发生什么?在更新myList时,应该如何锁定对myList的访问?

@Grammin 我会说这并不重要。 - Mob
如果你有另一个问题,最好的选择是开一个新帖子。这样它会被看到。 - Romain Hippeau
5个回答

6

注意:此代码假定您只有一个MyClass实例。根据您的帖子,这似乎是情况。

public class MyClass extends Thread implements Observer{
  private List<AnotherClass> myList = null;
  private Object lock = new Object();

  public MyClass(List<AnotherClass> myList){
    this.myList = new ArrayList(myList);
  }

  public void run(){
    while(true){
       //Do some stuff 
       synchronized(lock) {
        myList.add(NotImportantElement);
       }
    }
  }

  public void doJob{
    synchronized(lock) {
      for(int i=0; i<myList.size; i++){
        ElementClass x = myList.get(i);
        //Do some more stuff
      }
    }
  }
}

编辑:根据JB Nizet的建议,添加了复制List的操作,以防止外部实体更改列表。

编辑2:将变量设置为私有,以防止其他人访问它们。


5
不过存在一个缺陷:由于myList是来自外部的,无法保证另一个线程不会绕过锁直接访问myList。构造函数应该对其接收到的列表进行防御性复制。 - JB Nizet
@JB Nizet 感谢您指出这一点 - 我已经修复了示例。我还将实例变量设为私有。 - Romain Hippeau
仅使用“synchronized”有什么问题吗?这会在实例上加锁,似乎可以用一个更少的对象完成完全相同的工作。 - DJClayworth
@DJClayworth:他仍然需要在run方法中使用synchronized(this),因为如果他将synchronized放在方法上,锁将永远不会被释放:这是一个无限循环。 - JB Nizet

5
您可以:
  1. rundoJob都声明为synchronized。这将使用this作为锁;
  2. 将列表声明为final并在其上同步。这将使用列表作为锁。将锁字段声明为final是一个好习惯。这样,您的类的一些方法可以同步于一个对象,而其他方法可以使用另一个对象进行同步。这减少了锁争用,但增加了代码复杂性;
  3. 引入显式的java.util.concurrent.locks.Lock变量,并使用它的方法进行同步。这将提高代码的灵活性,但也会增加代码的复杂性;
  4. 完全不进行显式同步,而是使用JDK中的一些线程安全的数据结构。例如,BlockingQueueCopyOnWriteArrayList。这将减少代码复杂性并确保线程安全。
  5. 通过读/写volatile字段来实现同步。请参见 SO帖子。这将确保安全性,但将极大地增加复杂性。经过再次考虑,不要这样做 :)

2
通过使用并发列表,您可以避免很多麻烦。已点赞(4)。 - toto2
当运行时执行,它将被同步,并且永远不会释放锁定-#1将不起作用。#2存在问题,因为一些其他对象可能会对列表进行操作,这也需要进行同步。 - Romain Hippeau

1
你可以在两个方法中添加

synchronized

关键字,或者使用
synchronized(Myclass.class) {
}

前者基本上使用Myclass.class对象,但它不像后者那样细粒度。


1
将两个方法都声明为synchronized以锁定每个实例,或者使用synchronized(this){...}块仅在当前实例上进行锁定。

1
synchronized(myList) {
    // do stuff on myList
}

具体文档:内在锁和同步

然而,我鼓励您使用一个线程安全的并发数据结构来实现您想要的目标,以避免自己进行同步并获得(很多)更好的性能:并发包摘要


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