2015-08-13 54 views
1

根据我的理解,当我使用同步块时,它获取对象的锁定并在代码块完成执行时释放它。在下面的代码中同步块锁定对象和等待/通知

public class WaitAndNotify extends Thread{ 

    long sum; 

    public static void main(String[] args) { 
     WaitAndNotify wan = new WaitAndNotify(); 
     //wan.start(); 
     synchronized(wan){ 
      try { 
       wan.wait(); 
      } catch (InterruptedException ex) { 
       Logger.getLogger(WaitAndNotify.class.getName()).log(Level.SEVERE, null, ex); 
      } 
      System.out.println("Sum is : " + wan.sum); 
     } 
    } 

    @Override 
    public void run(){ 
     synchronized(this){ 
      for(int i=0; i<1000000; i++){ 
       sum = sum + i; 
      } 
      notify(); 
     } 

    } 
} 

如果run方法中的同步块首先获取锁,会发生什么?然后,main方法中的synchronized块必须等待(不是因为wait(),因为另一个线程获取了锁)。 run方法执行完成后,main方法是否会进入其synchronized块并等待它永远不会得到的通知?我在这里误解了什么?

+0

从这些基本的API转移到高级API,如java.util.concurrent.Executors和ExecutorService。查看代码示例:http://examples.javacodegeeks.com/core-java/util/concurrent/executorservice/java-executorservice-example-tutorial/ –

+1

@ sunrise76,java.util.concurrent中的类不是“更高级的:“他们在更高级别的抽象层面上运作。编写生产代码的devloper绝对应该使用更高级别的工具,但是_student_可以很好地理解构建这些更高级别工具的原语。就像学过汇编语言的人在用更高级的语言编写时做出更明智的决定一样,所以了解互斥锁,条件变量和原子操作的人可以更好地使用队列和线程池等。 –

+0

您的理解是正确的。如果没有其他线程在同一时间在'foo.wait()'调用中被阻塞,'foo.notify()'什么也不做。 –

回答

1

是的,在wait()引起挂起的线程之前可能执行notify(),所以您需要小心以防止发生这种情况。

出于这个原因(和其他人),通常使用java.util.concurrent的更高层次结构更好,因为它们通常会减少在脚中拍摄自己的可能性。

1

在这里你不会看到'等待永远'的问题,因为你正在用超时的方式调用wait()的版本;因此,即使没有收到通知,5秒后也会返回。 wait()调用的“等待永久”版本的确可能会出现您描述的问题。

+0

对不起,我忘了删除第5部分。如果是wait(),notify()是否有可能在wait()之前运行? –

+0

是的。如果发生这种情况,主执行线程将挂起。 –

1

这里有两个线程:WaitAndNotify(WAN)线程和Java的主执行线程。两人都在争夺同一把锁。

如果WAN线程首先获取锁定,主线程将被阻止。处于阻塞状态不等于处于等待状态。处于等待状态的线程将在继续前进之前等待通知。处于阻塞状态的线程会在锁定变为可用时主动尝试获取锁定(并继续尝试,直到锁定为止)。

假设run方法正常执行,它将调用notify(),这将不起作用,因为没有其他线程当前处于等待状态。即使有,广域网仍保持锁定状态,直到退出同步代码块。一旦WAN退出该块,那么Java将通知一个等待线程(如果有的话,没有)。

此时,主执行线程现在获得锁(不再被阻塞)并进入等待状态。现在,您已经使用等待的版本,将等待5000毫秒,然后继续。如果您使用了vanilla版本(wait()),它将永远等待,因为没有其他进程会通知它。

2

wait()隐含暂时退出相应的监视器,并重新进入它在返回:

wait()

当前线程必须拥有该对象的监视器。 线程发布 此监视器的所有权并等待另一个线程通知 等待此对象监视器的线程通过调用notify方法或notifyAll方法的 唤醒。线程,然后 等待,直到它可以重新获得显示器的所有权并恢复执行 执行

这就是为什么以及这种同步如何起作用。

+0

[wait()](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--)释放对象监视器时,[notify()](https ://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--)/ [notifyAll的()](https://docs.oracle.com/javase/8/ docs/api/java/lang/Object.html#notifyAll--)不。 [“唤醒的线程将无法继续,直到当前线程放弃对该对象的锁定。”](https://docs.oracle.com/javase/8/docs/api/java/lang/Object。 HTML#notifyAll--) – naaz

1

下面是示例程序的一个版本,它改为引入一个测试条件变量的循环。在从等待苏醒这样你就避免对事物的状态后一个线程坏的假设重新获取一个锁,而且也没有两个线程之间的顺序关系:

public class W extends Thread { 
    long sum; 
    boolean done; 

    public static void main(String[] args) throws InterruptedException { 
     W w = new W(); 
     w.start(); 
     synchronized(w) { 
      while (!w.done) { 
       w.wait(); 
      } 
      // move to within synchronized block so sum 
      // updated value is required to be visible 
      System.out.println(w.sum); 
     } 
    } 

    @Override public synchronized void run() { 
     for (int i = 0; i < 1000000; i++) { 
      sum += i; 
     } 
     done = true; 
     // no notify required here, see nitpick at end 
    } 
} 

这是不够的等待通知,因为你指出的原因(顺序依赖性,你依赖的是竞争条件,希望一个线程在另一个线程之前获取监视器)以及其他原因。首先,一个线程可以在没有收到通知的情况下从等待中唤醒,但是你不能认为有任何通知。

当一个线程等待时,它需要在一个循环中完成,在循环的测试中,它检查一些条件。另一个线程应该设置该条件变量,以便第一个线程可以检查它。 the Oracle tutorial的建议是:

注意:始终在测试等待条件的循环中调用wait。不要认为中断是针对您正在等待的特定情况,或者情况依然如此。

其他吹毛求疵:

  • 当你的例子写的,JVM不需要进行更改您的总和变量可见的主线程。如果添加一个同步实例方法来访问sum变量,或者访问同步块内的和,那么主线程将保证能够看到sum的更新值。

  • 看着你的日志记录,没有什么关于InterruptedException的严重问题,它并不意味着任何错误。当您在线程上调用中断,设置其中断标志,并且该线程当前正在等待或正在休眠,或者在标志仍然置位时进入等待或休眠方法时,会导致InterruptedException。在我的答案顶部的玩具示例中,我将异常置于throws子句中,因为我知道这不会发生。

  • 当线程终止时,它发出一个notifyAll,任何等待该对象的东西都会收到(同样,这就是连接的实现方式)。部分原因是使用Runnable而不是Thread。

  • 在这个特殊的例子中,在求和线程上调用Thread#join会更有意义,而不是调用wait。

这里的例子重新编写,使用加入代替:

public class J extends Thread { 
    private long sum; 

    synchronized long getSum() {return sum;} 

    public static void main(String[] args) throws InterruptedException { 
     J j = new J(); 
     j.start(); 
     j.join(); 
     System.out.println(j.getSum()); 
    } 

    @Override public synchronized void run() { 
     for (int i = 0; i < 1000000; i++) { 
      sum += i; 
     }   
    } 
} 

线程#加入呼叫等待,锁定线程对象。当求和线程终止时,它发送一个通知并将其isAlive标志设置为false。同时在join方法中,主线程正在等待求和线程对象,它接收到通知,检查isAlive标志,并且实现它不必再等待,因此它可以离开join方法并打印结果。