【问题标题】:Synchronization on static and instance method静态和实例方法的同步
【发布时间】:2012-06-25 20:34:42
【问题描述】:

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

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 上锁定。因此会达到不一致的状态。

如果我想为下面的代码 sn-p 实现一致的状态,如何实现?

【问题讨论】:

  • 为什么addObject是实例方法?为什么不是静态的?无论如何,您为什么要围绕并发对象进行同步?它们已经是线程安全的。
  • 状态不一致是什么意思?
  • 我假设他的意思是 Queue&lt;Object&gt; objectList = new List&lt;Object&gt; 而不是 ConcurrentLinkedQueue&lt;Object&gt; objectList = new ConcurrentLinkedQueue&lt;Object&gt; 所以我们得到了他所问实际问题的答案。

标签: java thread-safety


【解决方案1】:

首先,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);
       }

     }
}

【讨论】:

  • @assylias: “复制粘贴”,抱歉 =)
【解决方案2】:

编辑:我假设您的意思是 Queue&lt;Object&gt; objectList 而不是 ConcurrentLinkedQueue&lt;Object&gt; objectListConcurrentLinkedQueue&lt;Object&gt; 已经为您完成了所有线程安全,这意味着您可以随意调用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;

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

在这种情况下,您的读取需要使用 synchronized,但对于更简单的事情,例如 peek() 在简单的 object 上添加或删除,而不是在队列中修改,synchronized可以,在大多数情况下被删除。事实上,对于stringintbool 等,应该删除用于简单读取的synchronized 条件。

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

【讨论】:

  • 他已经在getHeadObject的声明中使用了synchronized关键字,它只是在safe.class上同步。
  • 我认为 Hans 是正确的,因为我们在这种情况下需要非常小心,如果静态字段需要独占访问,任何同步实例方法都应该有一个显式同步块来访问静态字段。
  • 这根本没有帮助....两个安全对象将访问同一个objectList,如果您在两个对象上同步,synchronized 将允许您同时使用两者进行编写时间,因为你没有明确使用(据虚拟机所知),同一个对象。
  • public static synchronized void myMethod() { . . . }public static void myMethod() { synchronized(this) { . . . } } 相同,因此如果两个不同的对象修改同一个类成员,那么两个线程将简单地锁定不同的对象并尝试同时修改同一事物。
  • this 方法中没有 this
【解决方案3】:

几个cmets:

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

【讨论】:

    猜你喜欢
    • 2010-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多