【问题标题】:Java: synchronize object inside another synchronized objectJava:在另一个同步对象中同步对象
【发布时间】:2015-02-27 14:51:34
【问题描述】:

我正在尝试熟悉 wait() 和 notify() 方法,并且我编写了一个简单的类,其作用类似于一个简单的生产者-消费者练习的监视器,其中有 N 个生产者和 N 个消费者。无论如何,按照练习中的要求,显示器只能存储 3 个项目。所以生产者必须等待;相反,如果监视器中有 0 个项目,则消费者必须等待。

public class Monitor {

  private List<Integer> items;
  private int capacity;
  private Object waitProducer;
  private Object waitConsumer;
  private int counter;

  public Monitor() {
      this.items = new ArrayList<Integer>();
      this.capacity = 3;
      this.waitProducer = new Object();
      this.waitConsumer = new Object();
      this.counter = 0;
  }

  public void produce() throws InterruptedException {
      synchronized (this) {

          if (this.items.size() == this.capacity) {
              synchronized (this.waitProducer) {
                  System.out.println("Producer " +   Thread.currentThread().getId() + " aspetta");
                  this.waitProducer.wait(); /***/
              }
          }

          counter++;

          System.out.println("Thread " + Thread.currentThread().getId()
                + " produces object " + counter);           

          this.items.add(counter);
          synchronized (this.waitConsumer) {
            this.waitConsumer.notify();
          }

      }
  }

  public void consume() throws InterruptedException {
      synchronized (this) {
          if (this.items.size() == 0) {

              synchronized (this.waitConsumer) {
                  this.waitConsumer.wait(); /***/

              }
          }         

          System.out.println("Thread " + Thread.currentThread().getId()
                + " consume object " + this.items.get(0));

          this.items.remove(0);
          synchronized (this.waitProducer) {
              this.waitProducer.notify();
          }
      }
   }
}

我认为 /***/ 存在问题:事实上,当调用 wait() 方法时,线程释放了 waitProducer(或 waitConsumer)上的锁,但它没有在 Monitor 对象上。这就是为什么当(第一个消费者的)第一个生产者调用 wait() 时,Monitor 对象不再可获取。显然,对 Monitor 对象的访问必须是互斥的,以便正确更新列表和计数器。那么,正确的方法是什么?谢谢

【问题讨论】:

  • 为什么外面的synchronized(this)在那里?在this.waitProducer.wait() 等待时,没有其他线程可以进入。
  • @LoryLory 在谷歌上快速搜索 volatile 与同步
  • @LoryLory 你应该考虑java.util.concurrent.Lock 并使用多个Conditions。它会更容易做你想做的事。
  • @nafas 同样,它们不是彼此的替代品。他们提供非常不同的服务。同步可防止竞争条件并确保变量或数据结构内的数据的稳定性。这可以被认为是执行控制。易失性与内存可见性有关,它可以防止数据被缓存,因此在访问时它始终是最新的。然而,这并不能防止潜在的竞争条件。
  • @LoryLory 这是Condition interface的JavaDoc

标签: java multithreading wait synchronized notify


【解决方案1】:

一次只有一个线程可以执行synchronized 块内的代码。

此外,在对象上调用 wait() 时,必须获得对该对象的排他锁(通过 synchronized(object))。

这意味着,解锁/解除阻塞/通知代码必须可以被称为wait()的原始块之外的其他线程访问。否则,执行将永远等待。

在您的代码中,没有其他线程可以到达this.waitConsumer.notify();,而一个线程正在等待this.waitProducer.wait();。因为所有这些都包含在synchronized(this) 块中,所以其他线程将继续争夺锁定this

【讨论】:

  • 好的,我或多或少明白了。我不知道如何把东西放在一起......因为当生产者调用 waitProduces.wait() 时,它还应该释放 Monitor 对象上的锁......而且对 Monitor 的访问也必须同步......跨度>
  • @LoryLory wait() does not release a lock 是的。否则,根据定义,没有线程能够notify
  • 好的,我认为使用条件变量是最好的解决方案。那么,只有使用wait()和notify()就可以得到等价的解决方案吗?
  • 我认为下面的代码最像:pastebin.com/AwWH7XEA
  • Re,“一次只能在synchronized 块内执行一个线程”在技术上不正确!如果两个不同的线程在不同的对象上同步,则可以同时在同一个synchronized 代码块中。当要保护的数据与同步对象相关联时,这可能是设计使然,也可能是错误的。例如,当一些菜鸟写synchronized (foo) { ...; foo=new Foo(); ... }
【解决方案2】:

@james large 如果两个线程在不同的对象上同步,那两个线程可以在同一个块中同步是什么意思?

假设您有一个如下所示的 synchronized 块:

synchronized(obj) {
    ...body of synchronized block...
}
...whatever happens next...

当 Java 线程 T 执行该语句时,它首先计算表达式 obj。该表达式必须要么返回null,要么返回对对象的引用。如果是null,则会抛出 NullPointerException。否则,线程 T 将尝试锁定对象。

没有两个线程可以同时锁定同一个对象。如果对象已经被其他线程锁定,则线程 T 将无法继续,直到对象被解锁。

当线程 T 最终能够锁定对象时,它将执行同步块的主体,然后它会解锁对象并继续下一步发生的事情。

这里的重要概念是表达式obj。假设线程 T 计算 obj,获得对对象 O 的引用,锁定它,然后进入同步块。然后线程 U 出现,评估obj,得到一个不同对象,P。线程 U 将锁定对象 P,并且将能够进入同步块,而线程 T 仍然在那里同一时间。

那怎么会发生呢?

按照预期,它可能如下所示:

class MyClass {
    private final Object lock = new Object();

    void foobar() {
        synchronized(lock) { ... }
    }
}

如果线程 T 和线程 U 在 MyClass 的不同实例上运行,那么每个实例将有自己的 lock 对象。通常,在这种情况下,同步块的主体只会对MyClass 的其他实例变量进行操作。由于两个线程对不同的数据进行操作,所以它们同时处于同一个同步块中并没有什么坏处。

同步的目的不是让两个线程同时脱离同一个方法:目的是让两个线程不操作同一个数据 同时。


有时新手程序员会这样写:

static Integer count = new Integer(0);

...
synchronized(count) {
    count += 1;
    ...operate on static data...
}

这几乎总是一个错误。当线程 T 进入块时,它将在静态变量count 引用的 Integer 对象上进行同步。但接下来它会更新count 以引用一个不同 整数对象。然后是线程 U。U 在 new count 上同步,两个线程在同一个块中,在不应该的时候对相同的 static 数据进行操作。

【讨论】:

    猜你喜欢
    • 2011-12-12
    • 1970-01-01
    • 2015-03-13
    • 1970-01-01
    • 2012-07-09
    • 1970-01-01
    • 2016-11-24
    • 2013-04-25
    • 2016-05-08
    相关资源
    最近更新 更多