多线程编程 -wait(),notify()/notityAll()方法

简介:

多线程编程 -wait(),notify()/notityAll()方法

先说几点:

一、

注意:这些方法属于Object,而不属于Thread。

二、

notify(); //当调用这个方法时,等待队列里面可能没有等待的线程,那是不是要判断等待对列是否为空才判断执不执行呢,没必要,因为这个耗费不大,就相当于多执行一条语句,所以没必要判断,如果要写判断语句,那么代码还复杂些,所以这个影响不大的情况下,我们没必要判断。

三、

每一个对象除了有一个锁之外,还有一个等待队列(wait set),当一个对象刚创建的时候,他的等待队列是空的。

我们可以利用这些方法来解决“生产者和消费者的问题”。

wait方法呢,是在当前线程锁住对象的锁后,才调用该对象的wait方法的。即在同步代码块中或者同步方法中进行调用的。调用后,该对象的等待队列中就有了一个所在线程,那个线程进入等待状态,此时,只有该对象调用notify方法,才可以把那个线程从队列里面拿出来,使这个线程成为可运行线程。

notifyAll方法就是把该对象等待队列里面的所有线程唤醒,成为可运行线程。


------------------------------------------------------------------------------------------------------------------------------------------


  关于这两个方法,有很多的内容需要说明.在下面的说明中可能会有很多地方不能一下子
明白,但在看完本节后,即使不能完全明白,你也一定要回过头来记住下面的两句话:

[wait(),notify()/notityAll()方法是普通对象的方法(Object超类中实现),而不是线程对象的方法]

[wait(),notify()/notityAll()方法只能在同步方法中调用]

  [线程的互斥控制]
 
  多个线程同时操作某一对象时,一个线程对该对象的操作可能会改变其状态,而该状态会影响另一线程对该对象的真正结果.
  这个例子我们在太多的文档中可以看到,就象两个售票员同时售出同一张票一样.

  线程A                           线程B

1.线程A在数据库中查询存票,发现票C可以卖出
2.线程A接受用户订票请求,准备出票.
3. 这时切换到了线程B执行
4. 线程B在数据库中查询存票,发现票C可以卖出                 
5. 线程B将票卖了出去
6.切换到线程A执行,线程A卖了一张已经卖出的票

所以需要一种机制来管理这类问题的发生,当某个线程正在执行一个不可分割的部分时,其它线程不能同时执行这一部分.

像这种控制某一时刻只能有一个线程执行某个执行单元的机制就叫互斥控制或共享互斥(mutual exclusion)

 在JAVA中,用synchornized关键字来实现互斥控制(暂时这样认为,JDK1.5已经发展了新的机制)

  [synchornized关键字]
  把一个单元声明为synchornized,就可以实现在同一时间只有一个线程操作该方法.
 
  事实上synchornized就是一把锁,但是是谁的锁,锁谁,这是一个非常复杂的问题.

  每个对象只有一把监视锁(monitor lock),一次只能被一个线程获取.当一个线程获取了这一个锁后,其它线程就只能等待这个线程释放该锁后才能再获取该锁.

  那么synchornized关键字到底锁什么?得到了谁的锁?
 
  对于同步块,synchornized获取的是参数中的对象的锁:

  synchornized(obj){
    //...............
  }
  线程执行到这里时,首先要获取obj这个实例的锁,如果没有获取到,线程只能等待.如果多个线程执行到这里,只能有一个线程获取obj的锁,然后执行{}中的语句,所以,obj对象的作用范围不同,控制程序不同.
 
  假如:

class Test{

  public void test(){
    Student stu = new Student();
    
    synchornized(stu){
        //...............
    }
  }

  这段程序实现不了同步,没有实现多线程之间的资源共享。因为每个线程执行到 Student stu = new Student()时会各自产生一个新对象然后获取这个对象有监视锁,各自皆大欢喜地执行

  而如果是类的属性:
  class Test{
    Student stu = new Student();

    public void test(){

        synchornized(stu){
          //...............
        }
    }
  }

  所有执行到Test实例的synchornized(o)的线程,只有一个线程可以获取到监视锁,实现线程的同步,多个线程共享一个stu对象

  有时我们会这样:

    public void test(){

        synchornized(this){
          //...............
        }
    }
  那么所有执行Test实例的线程只能有一个线程执行.而synchornized(o)和
synchornized(this)的范围是不同的,因为执行到Test实例的synchornized(o)的线程等待时,其它线程可以执行Test实例的synchornized(o1)部分,但多个线程同时只有一个可以执行Test实例的synchornized(this).
 
  而对于   synchornized(Test.class){
          //...............
    }这样的同步块而言,所有调用Test多个实例的线程此时只能有一个线程可以执行.

  [synchornized方法]

  如果一个方法声明为synchornized的,则等同于在这个方法上调用synchornized(this).

  如果一个静态方法被声明为synchornized,则等同于在这个方法上调用synchornized(类.class).

现在进入wait方法和notify/notifyAll方法.这两个(或叫三个)方法都是Object对象的方法,而不是线程对象的方法.如同锁一样,它们是在线程中调用某一对象上执行的.

  class Test{
    public synchornized void test(){
    //获取条件,int x 要求大于100;
    
        if(x < 100)
          wait();
    }
  }

  这里为了说明方法没有加在try{}catch(){}中,如果没有明确在哪个对象上调用wait()方法,则为this.wait();
  假如:

    Test t = new Test();
  现在有两个线程都执行到t.test();方法.其中线程A获取了t的对象锁,进入test()方法内.这时x小于100,所以线程A进入等待.

  当一个线程调用了wait方法后,这个线程就进入了这个对象的休息室(waitset),这是一个虚拟的对象,但JVM中一定存在这样的一个数据结构用来记录当前对象中有哪些程线程在等待.

  当一个线程进入等待时,它就会释放锁,让其它线程来获取这个锁.

  所以线程B有机会获得了线程A释放的锁,进入test()方法,如果这时x还是小于100,线程B也进入了t的休息室.这两个线程只能等待其它线程调用notity[All]来唤醒.

  但是如果调用的是有参数的wait(time)方法,则线程A,B都会在休息室中等待这个时间后自动唤醒.


  [为什么真正的应用都是用while(条件)而不用if(条件)]

在实际的编程中我们看到大量的例子都是用     
        while(x < 100)
          wait();
    go();而不是用if,为什么呢?
  在多个线程同时执行时,if(x <100)是不安全的.因为如果线程A和线程B都在t的休息室中等待,
这时另一个线程使x==100了,并调用notifyAll方法,线程A继续执行下面的go().而它执行完成后,x有可能
又小于100,比如下面的程序中调用了--x,这时切换到线程B,线程B没有继续判断,直接执行go();就产生一个
错误的条件,只有while才能保证线程B又继续检查一次.
 

  [notify/notifyAll方法]
  这两个方法都是把某个对象在休息区内的线程唤醒,notify只能唤醒一个,但究竟是哪一个不能确定,而notifyAll则唤醒这个对象上的休息室中所有的线程.


  一般为了安全性,我们在绝对多数时候应该使用notifiAll(),除非你明确知道只唤醒其中的一个线程.

那么是否是只要调用一个对象的wait()方法,当前线程就进入了这个对象的休息室呢?事实中,要调用一个对象的wait()方法,只有当前线程获取了这个对象的锁,换句话说一定要在这个对象的同步方法或以这个对象为参数的同步块中.
 

class MyThread extends Thread{
Test t = new Test();
  public void run(){
    t.test();
    System.out.println("Thread say:Hello,World!");
  }
}


public class Test {

  int x = 0;
  public void test(){
    if(x==0)
    try{
      wait();
    }catch(Exception e){}
  }
  public static void main(String[] args) throws Exception{
    new MyThread().start();
  }
}

这个线程就不会进入t的wait方法而直接打印出Thread say:Hello,World!.
而如果改成:


public class Test {

  int x = 0;
  public synchornized void test(){
    if(x==0)
    try{
      wait();
    }catch(Exception e){}
  }
  public static void main(String[] args) throws Exception{
    new MyThread().start();
  }
}
我们就可以看到线程一直等待,注意这个线程进入等待后没有其它线程唤醒,除非强行退出
JVM环境,否则它一直等待.


所以请记住:
1,线程要想调用一个对象的wait()方法就要先获得该对象的监视锁,而一旦调用wait()后又立即释放该锁

2,如果要把notify/notifyAll和wait方法放在一起用的话,必须先调用notify/notifyAll后调用wait,因为如果调用完wait,该线程就已
经不是current thread了

3,wait(),notify(),notifyAll()方法必须出现在同步方法或同步代码块中


目录
相关文章
|
3天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
6天前
|
数据采集 存储 数据处理
Python中的多线程编程及其在数据处理中的应用
本文深入探讨了Python中多线程编程的概念、原理和实现方法,并详细介绍了其在数据处理领域的应用。通过对比单线程与多线程的性能差异,展示了多线程编程在提升程序运行效率方面的显著优势。文章还提供了实际案例,帮助读者更好地理解和掌握多线程编程技术。
|
5天前
|
API Android开发 iOS开发
深入探索Android与iOS的多线程编程差异
在移动应用开发领域,多线程编程是提高应用性能和响应性的关键。本文将对比分析Android和iOS两大平台在多线程处理上的不同实现机制,探讨它们各自的优势与局限性,并通过实例展示如何在这两个平台上进行有效的多线程编程。通过深入了解这些差异,开发者可以更好地选择适合自己项目需求的技术和策略,从而优化应用的性能和用户体验。
|
10天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
16天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
43 9
|
19天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
16天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
48 1
C++ 多线程之初识多线程
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
20 3
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
19 2