Java使用wait/notify实现线程间通信上篇

线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,使用线程间进行通信后,系统之间的交互性会更强大,大大提高CPU利用率。

等待/通知机制

(1)不使用等待/通知机制实现线程间通信

样例代码如下:

public class TestC2 {
    public static void main(String[] args) throws Exception {
        MyList myList = new MyList();
        ThreadA threadA = new ThreadA(myList);
        threadA.setName("A");
        ThreadB threadB = new ThreadB(myList);
        threadB.setName("B");
        threadB.start();
        threadA.start();
    }
}
class MyList {
    volatile private List list = new ArrayList();
    public void add() {
        list.add("NanJing");
    }
    public int size() {
        return list.size();
    }
}
class ThreadA extends Thread {
    private MyList myList;
    public ThreadA(MyList myList) {
        this.myList = myList;
    }
    @Override
    public void run() {
        try {
            for (int i=0; i<10; i++) {
                myList.add();
                System.out.println("添加了" + (i+1) + "个元素");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class ThreadB extends Thread {
    private MyList myList;
    public ThreadB(MyList myList) {
        this.myList = myList;
    }
    @Override
    public void run() {
        try {
            while (true) {
                int myListSize = myList.size();
                if (myListSize == 5) {
                    System.out.println("myList长度等于5了,线程需要退出了");
                    throw new InterruptedException();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

分析:

程序运行后的效果如上图所示:

虽然 ThreadA 和 ThreadB 实现了通信,但有一个弊端就是,ThreadB 需要不停地通过 while 语句轮询机制来检测某一个条件,这样会浪费CPU资源。

如果轮询的时间间隔很小,更浪费CPU资源;如果轮询的时间间隔很大,有可能会取不到想要得到的数据。所以就需要有一种机制来实现减少CPU的资源浪费,而且还可以实现在多个线程间通信,它就是 “等待/通知”(wait/notify)机制。

(2)什么是等待/通知机制

等待/通知机制在生活中比比皆是,比如在就餐时就会出现

厨师和服务员之间的交互要在 “菜品传递台”上,在这期间会有几个问题:

问题一:厨师做完一道菜的时间不确定,所以厨师将菜品放到“菜品传递台”上的时间也不确定。

问题二:服务员取到菜的时间取决于厨师,所以服务员就有“等待”(wait)的状态。

问题三:服务员如何能取到菜呢?这又得取决于厨师,厨师将菜放在“菜品传递台”上,其实就相当于一种通知(notfiy),这时服务员才可以拿到菜并交给就餐者。

问题四:在这个过程中出现了“等待/通知”机制。

(3)等待/通知机制的实现

方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获取该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出 IllegalMonitorStateException,它是 RuntimeException的一个子类,因此,不需要 try-catch 语句进行捕捉异常。

方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获取该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并是它等待获取该对象的对象锁。需要说明是:在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则几遍该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify 或 notifyAll。

总结一下:

wait:使线程停止运行

notify:使停止的线程继续运行

验证1:在调用wait()之前,线程必须获取该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException。

@Test
public void test1() {
    try {
        String str = new String("");
        str.wait();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

执行结果:

验证2:调用了wait(),线程会在调用wait()所在的代码行处停止执行,直到接到通知或被中断为止。

@Test
public void test2() {
    String lockStr = new String("");
    System.out.println("sync 上面");
    try {
         synchronized (lockStr) {
             System.out.println("进入 sync");
             lockStr.wait();
             System.out.println("wait 下的代码");
         }
         System.out.println("sync 下面的代码");
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
}

执行结果:

结果分析:

线程执行了wait()方法后,程序就停止不前,不继续向下运行了。如何使呈等待wait状态的线程继续运行呢?答案就是使用notify()方法。

@Test
public void test3() {
   try {
       Object lock = new Object();
       ThreadC3A threadC3A = new ThreadC3A(lock);
       threadC3A.start();
       Thread.sleep(3000);
       ThreadC3B threadC3B = new ThreadC3B(lock);
       threadC3B.start();
   } catch (Exception e) {
       e.printStackTrace();
   }
}
class ThreadC3A extends Thread {
    private Object lock;
    public ThreadC3A(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        try {
            synchronized (lock) {
                System.out.println("开始 wait,Time=[" + System.currentTimeMillis() + "]");
                lock.wait();
                System.out.println("结束 wait,Time=[" + System.currentTimeMillis() + "]");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class ThreadC3B extends Thread {
    private Object lock;
    public ThreadC3B(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("开始 notify,Time=[" + System.currentTimeMillis() + "]");
            lock.notify();
            System.out.println("结束 notify,Time=[" + System.currentTimeMillis() + "]");
        }
    }
}

执行结果:
开始 wait,Time=[1659520582642]
开始 notify,Time=[1659520585652]
结束 notify,Time=[1659520585652]
结束 wait,Time=[1659520585656]

验证3:notify方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则有线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。

public class TestC5 {
    @Test
    public void test1() {
        Object obj = new Object();
        MyArrayList list = new MyArrayList();
        ThreadC5B threadC5B = new ThreadC5B(obj, list);
        threadC5B.start();
        ThreadC5C threadC5C = new ThreadC5C(obj, list);
        threadC5C.start();
        ThreadC5A threadC5A = new ThreadC5A(obj, list);
        threadC5A.start();
        while (Thread.activeCount() > 1) {
        }
    }
}
class ThreadC5A extends Thread {
    private Object lock;
    private MyArrayList list;
    public ThreadC5A(Object lock, MyArrayList list) {
        this.lock = lock;
        this.list = list;
    }
    @Override
    public void run() {
        try {
            synchronized (lock) {
                for (int i=0; i<10; i++) {
                    list.add();
                    System.out.println("ThreadC5A 新增第[" + (i+1) + "]个元素");
                    if (list.size() == 5) {
                        lock.notify();
                        System.out.println("ThreadC5A 发出通知,通知等待的线程 ThreadC5B 或 ThreadC5C");
                    }
                    Thread.sleep(1000);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class ThreadC5B extends Thread {
    private Object lock;
    private MyArrayList list;
    public ThreadC5B(Object lock, MyArrayList list) {
        this.lock = lock;
        this.list = list;
    }
    @Override
    public void run() {
        try {
            while (true) {
                synchronized (lock) {
                    System.out.println("ThreadC5B 等待被通知");
                    lock.wait();
                    System.out.println("ThreadC5B 收到通知,退出");
                    return;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class ThreadC5C extends Thread {
    private Object lock;
    private MyArrayList list;
    public ThreadC5C(Object lock, MyArrayList list) {
        this.lock = lock;
        this.list = list;
    }
    @Override
    public void run() {
        try {
            while (true) {
                synchronized (lock) {
                    System.out.println("ThreadC5C 等待被通知");
                    lock.wait();
                    System.out.println("ThreadC5C 收到通知,退出");
                    return;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:

结果分析:

可以看到处于wait状态的ThreadC5B线程被通知到继续执行,而ThreadC5C线程则一直将处于wait状态,无法继续执行。倘若我们将ThreadC5A线程中的 lock.notify() 改写为 lock.notifyAll(),则结果就不一样,如图所示:

发现ThreadC5B 和 ThreadC5C线程均获取到了对象锁,完成了wait()后面代码的执行。

验证4:上图的结果还可以验证在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。

结果分析:

可以看出 ThreadC5A 线程在list 长度为5的时候,则通知了 ThreadC5B 和 ThreadC5C 两个处于wait状态的线程,但是 ThreadC5B 和 ThreadC5C 线程并没有立马继续执行,因为此时 ThreadC5A 并没有释放对象锁,而是继续执行 synchronized的代码块,直到退出synchronized代码块后,ThreadC5A 才释放了对象锁,ThreadC5B 和 ThreadC5C 获得对象锁,才得以继续执行后续代码。

到此这篇关于Java使用wait/notify实现线程间通信上篇的文章就介绍到这了,更多相关Java wait/notify内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈Java线程间通信之wait/notify

    Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式.先来我们来看下相关定义: wait() :调用该方法的线程进入WATTING状态,只有等待另外线程的通知或中断才会返回,调用wait()方法后,会释放对象的锁. wait(long):超时等待最多long毫秒,如果没有通知就超时返回. notify() :通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提

  • Java通过wait()和notifyAll()方法实现线程间通信

    本文实例为大家分享了Java实现线程间通信的具体代码,供大家参考,具体内容如下 Java代码(使用了2个内部类): package Threads; import java.util.LinkedList; /** * Created by Frank */ public class ProdCons { protected LinkedList<Object> list = new LinkedList<>(); protected int max; protected bool

  • Java使用wait/notify实现线程间通信上篇

    线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,使用线程间进行通信后,系统之间的交互性会更强大,大大提高CPU利用率. 等待/通知机制 (1)不使用等待/通知机制实现线程间通信 样例代码如下: public class TestC2 { public static void main(String[] args) throws Exception { MyList myList = new MyList(); ThreadA

  • Java编程之多线程死锁与线程间通信简单实现代码

    死锁定义 死锁是指两个或者多个线程被永久阻塞的一种局面,产生的前提是要有两个或两个以上的线程,并且来操作两个或者多个以上的共同资源:我的理解是用两个线程来举例,现有线程A和B同时操作两个共同资源a和b,A操作a的时候上锁LockA,继续执行的时候,A还需要LockB进行下面的操作,这个时候b资源在被B线程操作,刚好被上了锁LockB,假如此时线程B刚好释放了LockB则没有问题,但没有释放LockB锁的时候,线程A和B形成了对LockB锁资源的争夺,从而造成阻塞,形成死锁:具体其死锁代码如下:

  • 深入解析Java的线程同步以及线程间通信

    Java线程同步 当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用.达到此目的的过程叫做同步(synchronization).像你所看到的,Java为此提供了独特的,语言水平上的支持. 同步的关键是管程(也叫信号量semaphore)的概念.管程是一个互斥独占锁定的对象,或称互斥体(mutex).在给定的时间,仅有一个线程可以获得管程.当一个线程需要锁定,它必须进入管程.所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程.这些其他的线程被

  • java多线程编程学习(线程间通信)

    一.概要 线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程间的通信就是成为整体的必用方案之一.可以说,使线程进行通信后,系统之间的交互性会更强大,在大大提高cpu利用率的同时还会使程序员对各线程任务在处理过程中进行有效的把控和监督. 二.等待/通知机制 1."wait/notify"机制:等待/通知机制,wait使线程暂停运行,而notify 使暂停的线程继续运行.用一个厨师和服务员的交互来说明: (1) 服务员取到菜的时间取决于厨师,所以服务员就有&

  • java线程间通信的通俗解释及代码示例

    线程间通信:由于多线程共享地址空间和数据空间,所以多个线程间的通信是一个线程的数据可以直接提供给其他线程使用,而不必通过操作系统(也就是内核的调度). 进程间的通信则不同,它的数据空间的独立性决定了它的通信相对比较复杂,需要通过操作系统.以前进程间的通信只能是单机版的,现在操作系统都继承了基于套接字(socket)的进程间的通信机制.这样进程间的通信就不局限于单台计算机了,实现了网络通信.线程通信主要分为以下几个部分,下面通过生活中图书馆借书的例子简单讲解以下: 通过共享对象通信 加入图书馆只有

  • Java中实现线程间通信的实例教程

    目录 前言 1. 如何让两个线程依次执行? 2. 如何让两个线程按照指定的方式有序相交? 3. 线程 D 在A.B.C都同步执行完毕后执行 4. 三个运动员分开准备同时开跑 5. 子线程将结果返回给主线程 总结 前言 虽然通常每个子线程只需要完成自己的任务,但是有时我们希望多个线程一起工作来完成一个任务,这就涉及到线程间通信. 关于线程间通信本文涉及到的方法和类包括:thread.join().object.wait().object.notify().CountdownLatch.Cyclic

  • Java编程线程间通信与信号量代码示例

    1.信号量Semaphore 先说说Semaphore,Semaphore可以控制某个资源可被同时访问的个数,通过acquire()获取一个许可,如果没有就等待,而release()释放一个许可.一般用于控制并发线程数,及线程间互斥.另外重入锁ReentrantLock也可以实现该功能,但实现上要复杂些. 功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了.另外等待的

  • Java线程间通信不同步问题原理与模拟实例

    本文实例讲述了Java线程间通信不同步问题原理与模拟.分享给大家供大家参考,具体如下: 一 点睛 下面两种情况可造成线程间不同步: 1 生产者没生产完,消费者就来消费. 2 消费者没消费完,生产者又来生产,覆盖了还没来得及消费的数据. 二 代码 class Producer implements Runnable { private Person person = null; public Producer( Person person ) { this.person = person; } @

随机推荐