静态和实例方法的同步

时间:2012-06-25 20:34:43

标签: java thread-safety

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

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锁,并且在实例方法上进行同步将锁定此问题,因此将达到不一致的状态。

如果我想为下面的代码片段实现一致的状态,那该怎么办呢?

3 个答案:

答案 0 :(得分:2)

首先,ConcurrentLinkedQueue不需要显式同步。见this answer

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

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

     }
}

答案 1 :(得分:1)

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

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

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

这会锁定objectList,并且只要程序在同步块内,就不允许在任何其他线程中读取或写入它。对所有其他方法执行相同操作synchronized

注意:

但是,由于你只进行了一次简单的get操作List.peek(),所以你真的不需要在objectList上进行同步,因为在竞争条件下,它将获得一个值List或其他。竞争条件的问题是当执行多个复杂的读/写操作时,它们之间的值会发生变化。

例如,如果您的班级PairInt包含PairInt.xPairInt.y字段,其约束条件为x = 2y,并且您想要

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

另一个主题是同时更新xy的值,

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

写线程在您的读线程myIntPairmyIntPair.x.toString()之间修改了myIntPair.y.toString(),您可能会得到一个看起来像(10, 8)的输出,这意味着如果您正在操作假设x == 2 * y可能导致程序崩溃。

在这种情况下,您的阅读需要使用synchronized,但对于更简单的内容,例如peek()上正在添加或删除的简单object,请勿在队列,synchronized可以,在大多数情况下被删除。实际上,对于stringintbool等,应删除简单读取的synchronized条件。

但是,对于非显式线程安全的操作(即已由java处理),写入应始终为synchronized。只要您获得多个资源,或者要求您的资源在整个操作过程中保持不变,就像对其执行多行逻辑那样,那么必须使用 synchronized

答案 2 :(得分:0)

一些评论:

  • Java约定:
    • 班级名称应该在CamelCase中(即调用您的班级Safe,而不是safe
    • static出现在方法声明
    • 中的synchronized之前
    • static位于字段声明中的final之前
  • 正如其他人已经说过的那样,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);
        }
    }
}