【问题标题】:Java synchronize on objectJava同步对象
【发布时间】:2011-12-12 16:33:08
【问题描述】:

如何同步同一个类的两个不同方法以锁定同一个对象?这是一个例子:

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

问题是如何在 doJob 执行时阻止 run() 访问 myList,反之亦然?

想象一下:我启动线程并开始将元素添加到我的列表中。在一个随机的时刻,我从另一个持有对我的线程的引用的类中调用 doJob()。

我应该怎么做锁?谢谢!

L.E.

好的,我理解了锁的概念,但现在我还有一个问题。

假设我有一个带有 public static myList 的类,并且只有该类的 一个 实例。从那个实例中,我创建了Threadn 实例,它们获取该列表的每个元素并对其进行一些处理。

现在,在特定时刻,myList 已更新。那些已经在处理 myList 元素的线程会发生什么?更新时如何锁定myList 的访问权限?

【问题讨论】:

  • @Grammin 我会说没关系。
  • 如果您还有其他问题,最好的办法是打开另一个帖子。这样就可以看到了。

标签: java methods locking synchronize


【解决方案1】:

你可以添加

同步

两种方法的关键字或使用

synchronized(Myclass.class) {
}

前者本质上使用了 Myclass.class 对象,但不如后者细粒度。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:
      synchronized(myList) {
          // do stuff on myList
      }
      

      具体文档:Intrinsic Locks and Synchronization

      但我鼓励您使用线程安全并发数据结构来实现您想要实现的目标,以避免自己同步并获得(很多)更好的性能:Concurrent package summary

      【讨论】:

        【解决方案4】:

        注意:此代码假定您只有一个 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
              }
            }
          }
        }
        

        编辑:添加了制作 List 的副本,以便外部实体无法按照 JB Nizet 更改列表

        编辑 2: 将变量设为私有,以便其他人无法访问它们

        【讨论】:

        • 但是有一个缺陷:由于 myList 来自外部,不能保证另一个线程直接访问 myList,绕过锁。构造函数应该对它接收到的列表做一个防御性的副本。
        • @JB Nizet 感谢您指出这一点 - 我修复了示例。还将实例变量设为私有
        • “同步”有什么问题?这锁定了实例,这似乎与少一个对象完全一样。
        • @DJClayworth :他仍然需要在 run 方法中使用 synchronized(this),因为如果他将 synchronized 放在方法上,锁将永远不会被释放:这是一个无限循环。
        【解决方案5】:

        你可以:

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

        【讨论】:

        • 赞成 (4.):使用并发列表可以避免很多麻烦。
        • 当 run 执行时,它将被同步并且永远不会释放锁 - #1 将不起作用。 #2 有一些其他对象可以在 List 上操作并且也需要同步的问题。
        猜你喜欢
        • 1970-01-01
        • 2012-07-09
        • 1970-01-01
        • 2013-04-25
        • 2016-05-08
        • 2015-03-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多