【问题标题】:How to correctly create a SynchronizedStack class?如何正确创建 SynchronizedStack 类?
【发布时间】:2011-02-02 16:09:03
【问题描述】:

我用 Java 制作了一个简单的同步 Stack 对象,仅用于培训目的。 这是我所做的:

public class SynchronizedStack {
    private ArrayDeque<Integer> stack;

    public SynchronizedStack(){
        this.stack = new ArrayDeque<Integer>();     
    }

    public synchronized Integer pop(){
        return this.stack.pop();
    }

    public synchronized int forcePop(){
        while(isEmpty()){
            System.out.println("    Stack is empty");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return this.stack.pop();
    }

    public synchronized void push(int i){
        this.stack.push(i);
        notifyAll();
    }

    public boolean isEmpty(){
        return this.stack.isEmpty();
    }

    public synchronized void pushAll(int[] d){
        for(int i = 0; i < d.length; i++){
            this.stack.push(i);
        }
        notifyAll();
    }

    public synchronized String toString(){
        String s = "[";
        Iterator<Integer> it = this.stack.iterator();   
        while(it.hasNext()){
            s += it.next() + ", ";
        }
        s += "]";
        return s;
    }
}

这是我的问题:

  • 不同步isEmtpy()方法可以吗?我认为这是因为即使另一个线程同时修改堆栈,它仍然会返回一个连贯的结果(没有操作进入既不是初始也不是最终状态的 isEmpty 状态)。还是让同步对象的所有方法同步是更好的设计?

  • 我不喜欢forcePop() 方法。我只是想创建一个线程,它能够在弹出元素之前等到一个项目被推入堆栈,我认为最好的选择是在线程的run() 方法中使用wait() 进行循环,但我不能,因为它会抛出IllegalMonitorStatException。做这种事情的正确方法是什么?

  • 还有其他意见/建议吗?

谢谢!

【问题讨论】:

  • 不要锁定方法,更喜欢锁定对象。阅读:download.oracle.com/javase/tutorial/essential/concurrency/…
  • 堆栈扩展了已经同步的 Vector。对于这个训练练习,不同的收集选择可能会更好。
  • Don Roby:是的,正如 axtavt 所指出的,我现在正在使用 ArrayDeque。
  • 肖恩·帕特里克·弗洛伊德:所以你说这样做更好:public void pop(){synchronized(this.stack){return this.stack.pop();}} 比这样:public void synchronized pop(){return this.stack.pop();} ?

标签: java multithreading synchronization


【解决方案1】:
  • Stack 本身已经同步,因此再次应用同步没有意义(如果您想要非同步堆栈实现,请使用 ArrayDeque

  • 这是不行的(除了上一点的事实),因为缺乏同步可能会导致内存可见性影响。

  • forcePop() 还不错。虽然它应该通过InterruptedException 而不捕获它以遵循可中断阻塞方法的合同。它允许您通过调用Thread.interrupt() 来中断在forcePop() 调用中阻塞的线程。

【讨论】:

  • 假设我在不依赖 Stack/ArrayDeque 实现的情况下对此进行了编码,但只是以某种方式使用数组,我确信没有可能导致不连贯的 isEmpty() 结果的传递状态。不同步(更快)还是无论如何同步(更安全)会更好吗?
  • 无论如何都要同步它 - 问题是,如果没有一些同步,你可能 - 永远 - 在你的 isEmpty() 调用中看到来自另一个线程的更新。
【解决方案2】:

假设stack.isEmpty() 不需要同步可能是正确的,但是您依赖于您无法控制的类的实现细节。 Stack 的 javadocs 声明该类不是线程安全的,因此您应该同步所有访问。

【讨论】:

    【解决方案3】:

    我认为你在混用一些习语。你用java.util.Stack 支持你的SynchronizedStack,而java.util.Vector 又是synchronized 支持。我认为您应该将wait()notify() 行为封装在另一个类中。

    【讨论】:

      【解决方案4】:

      不同步isEmpty() 的唯一问题是你不知道下面发生了什么。虽然您的推理是合理的,但它假设底层 Stack 也以合理的方式表现。在这种情况下可能就是这样,但一般情况下您不能依赖它。

      您问题的第二部分,阻塞弹出操作没有任何问题,请参阅this 了解所有可能策略的完整实现。

      还有一个建议:如果您正在创建一个可能在应用程序的多个部分(甚至多个应用程序)中重复使用的类,请不要使用synchronized 方法。改为这样做:

      public class Whatever {
        private Object lock = new Object();
      
        public void doSomething() {
          synchronized( lock ) {
            ...
          }
        }
      }
      

      这样做的原因是您并不真正知道您班级的用户是否想要在您的 Whatever 实例上进行同步。如果他们这样做,他们可能会干扰类本身的操作。这样你就有了自己的私人锁,没有人可以干涉。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-08-14
        • 2021-02-05
        • 1970-01-01
        相关资源
        最近更新 更多