Java多线程学习8:wait()和notify()/notifyAll()

轮询

线程本身是操作系统中独立的个体,但是线程与线程之间不是独立的个体,因为它们彼此之间要相互通信和协作。

想像一个场景,A线程做int型变量i的累加操作,B线程等待i到了10000就打印出i,怎么处理?一个办法就是,B线程while(i == 10000),这样两个线程之间就有了通信,B线程不断通过轮训来检测i == 10000这个条件。

这样可以实现我们的需求,但是也带来了问题:CPU把资源浪费了B线程的轮询操作上,因为while操作并不释放CPU资源,导致了CPU会一直在这个线程中做判断操作。如果可以把这些轮询的时间释放出来,给别的线程用,就好了。

wait/notify

在Object对象中有三个方法wait()、notify()、notifyAll(),既然是Object中的方法,那每个对象自然都是有的。如果不接触多线程的话,这两个方法是不太常见的。下面看一下前两个方法:

1、wait()

wait()的作用是使当前执行代码的线程进行等待,将当前线程置入"预执行队列"中,并且wait()所在的代码处停止执行,直到接到通知或被中断。在调用wait()之前,线程必须获得该对象的锁,因此只能在同步方法/同步代码块中调用wait()方法

2、notify()

notify()的作用是,如果有多个线程等待,那么线程规划器随机挑选出一个wait的线程,对其发出通知notify(),并使它等待获取该对象的对象锁。注意"等待获取该对象的对象锁",这意味着,即使收到了通知,wait的线程也不会马上获取对象锁,必须等待notify()方法的线程释放锁才可以。和wait()一样,notify()也要在同步方法/同步代码块中调用

总结起来就是,wait()使线程停止运行,notify()使停止运行的线程继续运行

wait()/notify()使用示例

看一段代码:

public class MyThread30_0 extends Thread
{
    private Object lock;

    public MyThread30_0(Object lock)
    {
        this.lock = lock;
    }

    public void run()
    {
        try
        {
            synchronized (lock)
            {
                System.out.println("开始------wait time = " + System.currentTimeMillis());
                lock.wait();
                System.out.println("结束------wait time = " + System.currentTimeMillis());
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}
public class MyThread30_1 extends Thread
{
    private Object lock;

    public MyThread30_1(Object lock)
    {
        this.lock = lock;
    }

    public void run()
    {
        synchronized (lock)
        {
            System.out.println("开始------notify time = " + System.currentTimeMillis());
            lock.notify();
            System.out.println("结束------notify time = " + System.currentTimeMillis());
        }
    }
}

写个main函数,同样的Thread.sleep(3000)也是为了保证mt0先运行,这样才能看到wait()和notify()的效果:

public static void main(String[] args) throws Exception
{
    Object lock = new Object();
    MyThread30_0 mt0 = new MyThread30_0(lock);
    mt0.start();
    Thread.sleep(3000);
    MyThread30_1 mt1 = new MyThread30_1(lock);
    mt1.start();
}

看一下运行结果:

开始------wait time = 1443931599021
开始------notify time = 1443931602024
结束------notify time = 1443931602024
结束------wait time = 1443931602024

第一行和第二行之间的time减一下很明显就是3s,说明wait()之后代码一直暂停,notify()之后代码才开始运行。

wait()方法可以使调用该线程的方法释放共享资源的锁,然后从运行状态退出,进入等待队列,直到再次被唤醒。

notify()方法可以随机唤醒等待队列中等待同一共享资源的一个线程,并使得该线程退出等待状态,进入可运行状态

notifyAll()方法可以使所有正在等待队列中等待同一共享资源的全部线程从等待状态退出,进入可运行状态

最后,如果wait()方法和notify()/notifyAll()方法不在同步方法/同步代码块中被调用,那么虚拟机会抛出java.lang.IllegalMonitorStateException,注意一下。

wait()释放锁以及notify()不释放锁

多线程的学习中,任何地方都要关注"锁",wait()和notify()也是这样。wait()方法是释放锁的,写一个例子来证明一下:

public class ThreadDomain31
{
    public void testMethod(Object lock)
    {
        try
        {
            synchronized (lock)
            {
                System.out.println(Thread.currentThread().getName() + " Begin wait()");
                lock.wait();
                System.out.println(Thread.currentThread().getName() + " End wait");
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}
public class MyThread31 extends Thread
{
    private Object lock;

    public MyThread31(Object lock)
    {
        this.lock = lock;
    }

    public void run()
    {
        ThreadDomain31 td = new ThreadDomain31();
        td.testMethod(lock);
    }
}

main函数调用一下:

public static void main(String[] args)
{
    Object lock = new Object();
    MyThread31 mt0 = new MyThread31(lock);
    MyThread31 mt1 = new MyThread31(lock);
    mt0.start();
    mt1.start();
}

看一下运行结果:

Thread-0 Begin wait()
Thread-1 Begin wait()

如果wait()方法不释放锁,那么Thread-1根本不会进入同步代码块打印的,所以,证明完毕。

接下来证明一下notify()方法不释放锁的结论:

public class ThreadDomain32
{
    public void testMethod(Object lock)
    {
        try
        {
            synchronized (lock)
            {
                System.out.println("Begin wait(), ThreadName = " + Thread.currentThread().getName());
                lock.wait();
                System.out.println("End wait(), ThreadName = " + Thread.currentThread().getName());
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

    public void synNotifyMethod(Object lock)
    {
        try
        {
            synchronized (lock)
            {
                System.out.println("Begin notify(), ThreadName = " + Thread.currentThread().getName());
                lock.notify();
                Thread.sleep(5000);
                System.out.println("End notify(), ThreadName = " + Thread.currentThread().getName());
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

写两个线程分别调用2个方法:

public class MyThread32_0 extends Thread
{
    private Object lock;

    public MyThread32_0(Object lock)
    {
        this.lock = lock;
    }

    public void run()
    {
        ThreadDomain32 td = new ThreadDomain32();
        td.testMethod(lock);
    }
}
public class MyThread32_1 extends Thread
{
    private Object lock;

    public MyThread32_1(Object lock)
    {
        this.lock = lock;
    }

    public void run()
    {
        ThreadDomain32 td = new ThreadDomain32();
        td.synNotifyMethod(lock);
    }
}

写个main函数调用一下:

public static void main(String[] args) throws Exception
{
    Object lock = new Object();
    MyThread32_0 mt0 = new MyThread32_0(lock);
    mt0.start();
    MyThread32_1 mt1 = new MyThread32_1(lock);
    mt1.start();
    MyThread32_1 mt2 = new MyThread32_1(lock);
    mt2.start();
}

看一下运行结果:

Begin wait(), ThreadName = Thread-0
Begin notify(), ThreadName = Thread-1
End notify(), ThreadName = Thread-1
Begin notify(), ThreadName = Thread-2
End notify(), ThreadName = Thread-2
End wait(), ThreadName = Thread-0

如果notify()方法释放锁,那么在Thread-1调用notify()方法后Thread.sleep(5000)必定应该有其他线程可以进入同步代码块了,但是实际上没有,必须等到Thread-1把代码执行完。所以,证明完毕。

interrupt()打断wait()

之前有说过,interrupt()方法的作用不是中断线程,而是在线程阻塞的时候给线程一个中断标识,表示该线程中断。wait()就是"阻塞的一种场景",看一下用interrupt()打断wait()的例子:

public class ThreadDomain33
{
    public void testMethod(Object lock)
    {
        try
        {
            synchronized (lock)
            {
                System.out.println("Begin wait()");
                lock.wait();
                System.out.println("End wait()");
            }
        }
        catch (InterruptedException e)
        {
            System.out.println("wait()被interrupt()打断了!");
            e.printStackTrace();
        }
    }
}
public class MyThread33 extends Thread
{
    private Object lock;

    public MyThread33(Object lock)
    {
        this.lock = lock;
    }

    public void run()
    {
        ThreadDomain33 td = new ThreadDomain33();
        td.testMethod(lock);
    }
}
public static void main(String[] args) throws Exception
{
    Object lock = new Object();
    MyThread33 mt = new MyThread33(lock);
    mt.start();
    Thread.sleep(5000);
    mt.interrupt();
}

看一下运行结果:

Begin wait()
wait()被interrupt()打断了!
java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:485)
    at com.xrq.example.e33.ThreadDomain33.testMethod(ThreadDomain33.java:12)
    at com.xrq.example.e33.MyThread33.run(MyThread33.java:15)

notifyAll()唤醒所有线程

利用Object对象的notifyAll()方法可以唤醒处于同一监视器下的所有处于wait的线程,举个例子证明一下:

public class ThreadDomain34
{
    public void testMethod(Object lock)
    {
        try
        {
            synchronized (lock)
            {
                System.out.println("Begin wait(), ThreadName = " + Thread.currentThread().getName());
                lock.wait();
                System.out.println("End wait(), ThreadName = " + Thread.currentThread().getName());
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

写两个线程,一个调用testMethod(Object lock)的线程,一个notifyAll()线程:

public class MyThread34_0 extends Thread
{
    private Object lock;

    public MyThread34_0(Object lock)
    {
        this.lock = lock;
    }

    public void run()
    {
        ThreadDomain34 td = new ThreadDomain34();
        td.testMethod(lock);
    }
}
public class MyThread34_1 extends Thread
{
    private Object lock;

    public MyThread34_1(Object lock)
    {
        this.lock = lock;
    }

    public void run()
    {
        synchronized (lock)
        {
            lock.notifyAll();
        }
    }
}

main函数开三个wait线程,用一个notifyAll的线程去唤醒:

public static void main(String[] args) throws Exception
{
    Object lock = new Object();
    MyThread34_0 mt0 = new MyThread34_0(lock);
    MyThread34_0 mt1 = new MyThread34_0(lock);
    MyThread34_0 mt2 = new MyThread34_0(lock);
    mt0.start();
    mt1.start();
    mt2.start();
    Thread.sleep(1000);
    MyThread34_1 mt3 = new MyThread34_1(lock);
    mt3.start();
}

看一下运行结果:

Begin wait(), ThreadName = Thread-0
Begin wait(), ThreadName = Thread-2
Begin wait(), ThreadName = Thread-1
End wait(), ThreadName = Thread-1
End wait(), ThreadName = Thread-2
End wait(), ThreadName = Thread-0

当然,唤醒的顺序不重要,因为notifyAll()把处于同一资源下wait的线程全部唤醒,至于唤醒的顺序,就和线程启动的顺序一样,是虚拟机随机的。

时间: 2024-08-08 13:54:48

Java多线程学习8:wait()和notify()/notifyAll()的相关文章

Java多线程8:wait()和notify()/notifyAll()

轮询 线程本身是操作系统中独立的个体,但是线程与线程之间不是独立的个体,因为它们彼此之间要相互通信和协作. 想像一个场景,A线程做int型变量i的累加操作,B线程等待i到了10000就打印出i,怎么处理?一个办法就是,B线程while(i == 10000),这样两个线程之间就有了通信,B线程不断通过轮训来检测i == 10000这个条件. 这样可以实现我们的需求,但是也带来了问题:CPU把资源浪费了B线程的轮询操作上,因为while操作并不释放CPU资源,导致了CPU会一直在这个线程中做判断操

java多线程15 :wait()和notify() 的生产者/消费者模式

什么是生产者/消费者模型 一种重要的模型,基于等待/通知机制.生产者/消费者模型描述的是有一块缓冲区作为仓库,生产者可将产品放入仓库,消费者可以从仓库中取出产品,生产者/消费者模型关注的是以下几个点: 1.生产者生产的时候消费者不能消费 2.消费者消费的时候生产者不能生产 3.缓冲区空时消费者不能消费 4.缓冲区满时生产者不能生产 生产者/模型作为一种重要的模型,它的优点在于: 1.解耦.因为多了一个缓冲区,所以生产者和消费者并不直接相互调用,这一点很容易想到,这样生产者和消费者的代码发生变化,

[转]Java多线程学习(总结很详细!!!)

Java多线程学习(总结很详细!!!) 此文只能说是java多线程的一个入门,其实Java里头线程完全可以写一本书了,但是如果最基本的你都学掌握好,又怎么能更上一个台阶呢? 本文主要讲java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的线程函数用法.概述等.首先让我们来了解下在操作系统中进程和线程的区别: 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程.(进程是资源分配的最小单位) 线程:同一类线程共享代码和数据空间,

java多线程学习(3)

1)竞争条件 在实际的多线程应用中,通常会有两个或多个线程需要对共同的对象进行共享访问,如果两个线程访问相同的对象,而且每一个都调用了一个会改变对象状态的方法, 那么,线程就会相互倾轧.根据各个线程访问数据的不同顺序,可能会产生腐蚀现象.这种情况通常称为竞争条件. 2)同步 为了多个线程对共享数据的腐蚀,就需要对数据的存取实现同步:常用的同步方法有3种: 1.Reenlock 用Reenlock保护代码块的基本机构如下: 1 Lock myLock=new ReenLock; 2 3 myLoc

黑马程序员之Java多线程学习

android培训  java培训 期待与您交流! 这一篇文章主要关于java多线程,主要还是以例子来驱动的.因为讲解多线程的书籍和文章已经很多了,所以我也不好意思多说,呵呵.大家可以去参考一些那些书籍.我这个文章主要关于实际的一些问题.同时也算是我以后复习的资料吧,.呵呵大家多多指教. 同时希望多结交一些技术上的朋友.谢谢. -------------------------------------------------------------------------------------

java多线程学习(2)

1)Callable和Future Runnable封装一个异步运行的任务:可以当成一个没有任何参数和返回值的异步方法,Callable和 Runnable类似,但是它有返回值和参数. Callable接口是一个参数化的类型,只有一个方法call. 1 public interface Callable<V> 2 3 { 4 5 V call()throws Exception; 6 7 } 类型参数v是指返回值的类型,例如Callable<Integer>代表最终返回一个Inte

java多线程学习(1)

1)多线程与多进程的区别 多线程和多进程有什么区别呢?本质的区别在于每个进程有它自己的变量的完备集,线程则共享相同的数据. 对程序来说,共享的变量会使得线程之间的通信比进程间的通信更加有效和简单:同时,线程相对于进程来说,更加的“轻量级”, 线程的创建和销毁要比 进程的 开销要小的多. 2)多线程程序的构造 多线程的构造通常有两种方法, 第一种方法是,构建一个Thread的子类,并重写它的run()方法: 1 class MyThread extends Thread 2 { 3 4 publi

java多线程学习--java.util.concurrent

CountDownLatch,api 文档:http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes. 假设我们要打印1-100,最

java 多线程学习(一)

1 public class ThreadA extends Thread { 2 private static int threadID = 0; 3 4 public ThreadA() { 5 super("ThreadID:" + (++threadID)); 6 } 7 8 public void run() { 9 try { 10 System.out.println(getName() + " 线程运行开始!"); 11 for (int i = 0

Java多线程学习幸运飞艇采集器修复

package javastudy01; class MyThread extends Thread {//重写Run方法public void run(){//1.获取当前线程的名字System.out.println(this.getName()+"我是一个线程."); } public static void main(String[] args) {Java多线程学习幸运飞艇采集器修复,需要请搜索[大神源码论坛]dsluntan.com 客服企娥3393756370 V信170