5

我对同步实例方法和静态方法感到困惑。我想编写一个线程安全类,如下所示:

public class safe {

  private final static ConcurrentLinkedQueue<Object> objectList=
      new ConcurrentLinkedQueue<Object>();

  /**
   * retrieves the head of the object and prints it
   */
    public synchronized static  void getHeadObject() {
      System.out.println(objectList.peek().toString());

    }

    /**
     * creates a new object and stores in the list.
     */
    public synchronized void addObject() {
      Object obj=new Object();
      objectList.add(obj);

    }
}

在静态方法上同步将锁定 safe.class 锁,而在实例方法上同步将在 this 上锁定。因此将达到不一致的状态。

如果我想为下面的代码片段实现一致的状态,如何实现?

4

3 回答 3

2

首先,ConcurrentLinkedQueue 不需要显式同步。看到这个答案

其次,您始终可以同步您正在访问的对象:

public class safe {

      private final static ConcurrentLinkedQueue<Object> objectList=
          new ConcurrentLinkedQueue<Object>();

      /**
       * retrieves the head of the object and prints it
       */
     public static  void getHeadObject() {
         synchronized(objectList){
          System.out.println(objectList.peek().toString());
         }

     }

        /**
         * creates a new object and stores in the list.
         */
     public void addObject() {
          Object obj=new Object();
       synchronized(objectList){
          objectList.add(obj);
       }

     }
}
于 2012-06-25T20:47:10.460 回答
1

编辑:我假设你的意思是Queue<Object> objectList而不是ConcurrentLinkedQueue<Object> objectList. ConcurrentLinkedQueue<Object>已经为您完成了所有线程安全,这意味着您可以objectList.peek()随心所欲地调用而不必担心竞争条件。如果您正在开发多线程程序,这很好,但对于学习线程安全性来说不是很好。

您的方法不必是synchronized,假设您一次有一个线程在对象的一个​​实例上运行,但是如果您需要有多个类的实例都引用同一个静态类变量,则需要synchronized覆盖该类像这样的变量:

public static void getHeadObject() {
    synchronized(safe.objectList) {
        System.out.println(objectList.peek().toString());
    }
}

一旦程序位于同步块内,这将锁定objectList并不允许在任何其他线程中对其进行读取或写入。对所有其他方法执行相同的操作synchronized

笔记:

但是,由于您只执行一个简单的 get 操作List.peek(),因此您实际上不需要objectList在竞争条件下同步 since,它会得到一个值List或另一个值。竞争条件的问题是当执行多个复杂的读/写操作时,它们之间的值会发生变化。

例如,如果您有一个PairInt具有PairInt.xPairInt.y字段的类,具有约束 that x = 2y,并且您想做

System.out.println(myIntPair.x.toString() + ", " + myIntPair.y.toString());

另一个线程同时更新 和 的xy

myIntPair.y = y + 3;
myIntPair.x = 2 * y;

myIntPair并且在您的读取线程之间修改了写入线程myIntPair.x.toString()myIntPair.y.toString()您可能会得到一个看起来像 的输出(10, 8),这意味着如果您在假设x == 2 * y可能会使程序崩溃的情况下进行操作。

在这种情况下,您的 read 需要使用 a synchronized,但对于更简单的事情,例如在队列中添加或删除、未修改peek()的简单内容,在大多数情况下可以删除。事实上,对于、、等,应该去掉简单读取的条件。objectsynchronizedstringintboolsynchronized

但是,写入应该始终synchronized针对不是明确线程安全的操作,即已经由 java 处理的操作。一旦您获得了多个资源,或者要求您的资源在整个操作过程中保持不变,因为您对其执行多行逻辑,那么您必须使用 synchronized

于 2012-06-25T20:39:29.470 回答
1

几点评论:

  • Java 约定:
    • 类名应该在 CamelCase 中(即调用你的类Safe,而不是safe
    • staticsynchronized在方法声明之前
    • staticfinal在字段声明之前
  • 正如其他人已经说过的那样,ConcurrentLinkedQueue它已经是线程安全的,因此在您给出的示例中不需要同步。
  • 混合静态和非静态方法的方式看起来很奇怪。
  • 假设您的实际用例更复杂,并且您需要一种方法来运行原子操作,那么您的代码不起作用,正如您所指出的,因为 2 个同步方法不在同一个监视器上同步:
public static synchronized getHeadObject(){} //monitor = Safe.class
public static synchronized addObject(){} //monitor = this

因此,要回答您的具体问题,您可以使用单独的静态对象作为锁:

public class Safe {

    private static final ConcurrentLinkedQueue<Object> objectList =
            new ConcurrentLinkedQueue<Object>();
    // lock must be used to synchronize all the operations on objectList
    private static final Object lock = new Object();

    /**
     * retrieves the head of the object and prints it
     */
    public static void getHeadObject() {
        synchronized (lock) {
            System.out.println(objectList.peek().toString());
        }
    }

    /**
     * creates a new object and stores in the list.
     */
    public void addObject() {
        synchronized (lock) {
            Object obj = new Object();
            objectList.add(obj);
        }
    }
}
于 2012-06-26T09:18:22.763 回答