当前位置: 首页 > news >正文

JAVA多线程之wait/notify

本文主要学习JAVA多线程中的 wait()方法 与 notify()/notifyAll()方法的用法。

①wait() 与 notify/notifyAll 方法必须在同步代码块中使用

②wait() 与  notify/notifyAll() 的执行过程

③中断 调用wait()方法进入等待队列的 线程

④notify 通知的顺序不能错

⑤多线程中测试某个条件的变化用 if 还是用 while?

 

 

①wait() 与 notify/notifyAll 方法必须在同步代码块中使用

wait() 与 notify/notifyAll() 是Object类的方法,在执行两个方法时,要先获得锁。那么怎么获得锁呢?

在这篇:JAVA多线程之Synchronized关键字--对象锁的特点文章中介绍了使用synchronized关键字获得锁。因此,wait() 与  notify/notifyAll() 经常与synchronized搭配使用,即在synchronized修饰的同步代码块或方法里面调用wait() 与  notify/notifyAll()方法。

 

②wait() 与  notify/notifyAll() 的执行过程

由于 wait() 与  notify/notifyAll() 是放在同步代码块中的,因此线程在执行它们时,肯定是进入了临界区中的,即该线程肯定是获得了锁的。

当线程执行wait()时,会把当前的锁释放,然后让出CPU,进入等待状态。

 当执行notify/notifyAll方法时,会唤醒一个处于等待该 对象锁 的线程,然后继续往下执行,直到执行完退出对象锁锁住的区域(synchronized修饰的代码块)后再释放锁。

从这里可以看出,notify/notifyAll()执行后,并不立即释放锁,而是要等到执行完临界区中代码后,再释放。故,在实际编程中,我们应该尽量在线程调用notify/notifyAll()后,立即退出临界区。即不要在notify/notifyAll()后面再写一些耗时的代码。

示例如下:(摘自《JAVA多线程核心技术》)

复制代码
 1 public class Service {
 2 
 3     public void testMethod(Object lock) {
 4         try {
 5             synchronized (lock) {
 6                 System.out.println("begin wait() ThreadName="
 7                         + Thread.currentThread().getName());
 8                 lock.wait();
 9                 System.out.println("  end wait() ThreadName="
10                         + Thread.currentThread().getName());
11             }
12         } catch (InterruptedException e) {
13             e.printStackTrace();
14         }
15     }
16 
17     public void synNotifyMethod(Object lock) {
18         try {
19             synchronized (lock) {
20                 System.out.println("begin notify() ThreadName="
21                         + Thread.currentThread().getName() + " time="
22                         + System.currentTimeMillis());
23                 lock.notify();
24                 Thread.sleep(5000);
25                 System.out.println("  end notify() ThreadName="
26                         + Thread.currentThread().getName() + " time="
27                         + System.currentTimeMillis());
28             }
29         } catch (InterruptedException e) {
30             e.printStackTrace();
31         }
32     }
33 }
复制代码

在第3行的testMethod()中调用 wait(),在第17行的synNotifyMethod()中调用notify()

从上面的代码可以看出,wait() 与  notify/notifyAll()都是放在同步代码块中才能够执行的。如果在执行wait() 与  notify/notifyAll() 之前没有获得相应的对象锁,就会抛出:java.lang.IllegalMonitorStateException异常。

在第8行,当ThreadA线程执行lock.wait();这条语句时,释放获得的对象锁lock,并放弃CPU,进入等待队列。

当另一个线程执行第23行lock.notify();,会唤醒ThreadA,但是此时它并不立即释放锁,接下来它睡眠了5秒钟(sleep()是不释放锁的,事实上sleep()也可以不在同步代码块中调用),直到第28行,退出synchronized修饰的临界区时,才会把锁释放。这时,ThreadA就有机会获得另一个线程释放的锁,并从等待的地方起(第24行)起开始执行。

 

接下来是两个线程类,线程类ThreadA调用testMethod()方法执行lock.wait();时被挂起,另一个线程类synNotifyMethodThread调用synNotifyMethod()负责唤醒挂起的线程。代码如下:

 

复制代码
 1 public class ThreadA extends Thread {
 2     private Object lock;
 3 
 4     public ThreadA(Object lock) {
 5         super();
 6         this.lock = lock;
 7     }
 8 
 9     @Override
10     public void run() {
11         Service service = new Service();
12         service.testMethod(lock);
13     }
14 }
15 
16 public class synNotifyMethodThread extends Thread {
17     private Object lock;
18 
19     public synNotifyMethodThread(Object lock) {
20         super();
21         this.lock = lock;
22     }
23 
24     @Override
25     public void run() {
26         Service service = new Service();
27         service.synNotifyMethod(lock);
28     }
29 }
复制代码

 

 

再接下来是测试类:

复制代码
 1 public class Test {
 2 
 3     public static void main(String[] args) throws InterruptedException {
 4 
 5         Object lock = new Object();
 6 
 7         ThreadA a = new ThreadA(lock);
 8         a.start();
 9 
10         NotifyThread notifyThread = new NotifyThread(lock);
11         notifyThread.start();
12 
13         synNotifyMethodThread c = new synNotifyMethodThread(lock);
14         c.start();
15     }
16 }
复制代码

 

③中断 调用wait()方法进入等待队列的 线程

示例代码如下:

复制代码
 1 public class Service {
 2 
 3     public void testMethod(Object lock) {
 4         try {
 5             synchronized (lock) {
 6                 System.out.println("begin wait()");
 7                 lock.wait();
 8                 System.out.println("  end wait()");
 9             }
10         } catch (InterruptedException e) {
11             e.printStackTrace();
12             System.out.println("出现异常");
13         }
14     }
15 }
16 
17 public class ThreadA extends Thread {
18 
19     private Object lock;
20 
21     public ThreadA(Object lock) {
22         super();
23         this.lock = lock;
24     }
25 
26     @Override
27     public void run() {
28         Service service = new Service();
29         service.testMethod(lock);
30     }
31 }
复制代码

注意,在第23行wait()方法是Object类的对象lock调用的。而下面的interrupt()方法是ThreadA类的对象调用的。在ThreadA里面,将Object的对象作为参数传给了testMethod()方法,ThreadA的run()方法去调用testMethod(),从而wait()使ThreadA的线程暂停了(暂停当前执行wait()的线程)。从这里可以看出一个区别:

Object类中与线程有关的方法:

1)notify/notifyAll

2)wait()/wait(long)

java.lang.Thread中与之相关的方法:

1)interrupt()

2)sleep()/sleep(long)

3)join()/suspend()/resume()....

测试类代码如下:

复制代码
 1 public class Test {
 2 
 3     public static void main(String[] args) {
 4 
 5         try {
 6             Object lock = new Object();
 7 
 8             ThreadA a = new ThreadA(lock);
 9             a.start();
10 
11             Thread.sleep(5000);
12 
13             a.interrupt();
14         } catch (InterruptedException e) {
15             e.printStackTrace();
16         }
17     }
18 }
复制代码

当执行第13行的interrupt()时,处于wait中的线程“立即”被唤醒(一般是立即响应中断请求),并抛出异常。此时,线程也就结束了。

 

④notify 通知的顺序不能错

假设在线程A中执行wait(),在线程B中执行notify()。但如果线程B先执行了notify()然后结束了,线程A才去执行wait(),那此时,线程A将无法被正常唤醒了(还可以通过③中提到的interrupt()方法以抛出异常的方式唤醒^~^)。

这篇文章: JAVA多线程之线程间的通信方式中的第③点提到了notify通知顺序出错会导致 调用wait()进入等待队列的线程再也无法被唤醒了。

 

⑤多线程中测试某个条件的变化用 if 还是用 while?

以前一直不明白 当在线程的run()方法中需要测试某个条件时,为什么用while,而不用if???直到看到了这个简单的例子,终于明白了。。。。

这个例子是这样的:

有两个线程从List中删除数据,而只有一个线程向List中添加数据。初始时,List为空,只有往List中添加了数据之后,才能删除List中的数据。添加数据的线程向List添加完数据后,调用notifyAll(),唤醒了两个删除线程,但是它只添加了一个数据,而现在有两个唤醒的删除线程,这时怎么办??

如果用 if 测试List中的数据的个数,则会出现IndexOutofBoundException,越界异常。原因是,List中只有一个数据,第一个删除线程把数据删除后,第二个线程再去执行删除操作时,删除失败,从而抛出 IndexOutofBoundException。

但是如果用while 测试List中数据的个数,则不会出现越界异常!!!神奇。

当wait等待的条件发生变化时,会造成程序的逻辑混乱---即,List中没有数据了,再还是有线程去执行删除数据的操作。因此,需要用while循环来判断条件的变化,而不是用if。

示例如下:摘自《JAVA多线程编程核心技术》

Add类,负责添加数据:

复制代码
public class Add {

    private String lock;
    public Add(String lock) {
        super();
        this.lock = lock;
    }

    public void add() {
        synchronized (lock) {
            ValueObject.list.add("anyString");
            lock.notifyAll();
        }
    }
}

public class ThreadAdd extends Thread {

    private Add p;

    public ThreadAdd(Add p) {
        super();
        this.p = p;
    }

    @Override
    public void run() {
        p.add();
    }
}
复制代码

 

Subtract类,负责删除数据----先要进行条件判断,然后执行wait(),这意味着:wait等待的条件可能发生变化!!!

复制代码
public class Subtract {

    private String lock;

    public Subtract(String lock) {
        super();
        this.lock = lock;
    }

    public void subtract() {
        try {
            synchronized (lock) {
                if(ValueObject.list.size() == 0) {//将这里的if改成while即可保证不出现越界异常!!!!
                    System.out.println("wait begin ThreadName="
                            + Thread.currentThread().getName());
                    lock.wait();
                    System.out.println("wait   end ThreadName="
                            + Thread.currentThread().getName());
                }
                ValueObject.list.remove(0);
                System.out.println("list size=" + ValueObject.list.size());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadSubtract extends Thread {

    private Subtract r;

    public ThreadSubtract(Subtract r) {
        super();
        this.r = r;
    }

    @Override
    public void run() {
        r.subtract();
    }
}
复制代码

 

封装的List队列:

public class ValueObject {

    public static List list = new ArrayList();

}

 

测试类:

复制代码
public class Run {

    public static void main(String[] args) throws InterruptedException {

        String lock = new String("");

        Add add = new Add(lock);
        Subtract subtract = new Subtract(lock);

        ThreadSubtract subtract1Thread = new ThreadSubtract(subtract);
        subtract1Thread.setName("subtract1Thread");
        subtract1Thread.start();

        ThreadSubtract subtract2Thread = new ThreadSubtract(subtract);
        subtract2Thread.setName("subtract2Thread");
        subtract2Thread.start();

        Thread.sleep(1000);

        ThreadAdd addThread = new ThreadAdd(add);
        addThread.setName("addThread");
        addThread.start();

    }
}

转载于:https://www.cnblogs.com/bangchen/p/6593955.html

相关文章:

  • 出现次数最多的k个数 Top K Frequent Words
  • 异步拷贝文件
  • 用lua实现ByteArray和ByteArrayVarint
  • 作业2
  • 缓冲区溢出漏洞(2)
  • Visual Studio VS如何重置所有设置
  • Twirp:一个很酷的基于Go的新RPC框架
  • Nginx+Tomcat搭建高性能负载均衡集群
  • 我的第一篇博客
  • 我讲个事情哈,编程其实是文科
  • 基于Docker和Debian打造个人专属操作系统
  • thinkphp在前端页面的js代码中可以使用 U方法吗? 可以使用模板变量如__URL__等吗?...
  • 编写符合Python风格的对象
  • 二叉树基础之序列化和反序列化二叉树
  • 数组作业
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • Angular 响应式表单 基础例子
  • chrome扩展demo1-小时钟
  • Java多态
  • Java多线程(4):使用线程池执行定时任务
  • laravel 用artisan创建自己的模板
  • linux安装openssl、swoole等扩展的具体步骤
  • MobX
  • win10下安装mysql5.7
  • 百度小程序遇到的问题
  • 分布式任务队列Celery
  • 浮动相关
  • 高度不固定时垂直居中
  • 聊聊hikari连接池的leakDetectionThreshold
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 如何胜任知名企业的商业数据分析师?
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 适配iPhoneX、iPhoneXs、iPhoneXs Max、iPhoneXr 屏幕尺寸及安全区域
  • 微信开源mars源码分析1—上层samples分析
  • ​iOS实时查看App运行日志
  • (0)Nginx 功能特性
  • (3)llvm ir转换过程
  • (Python) SOAP Web Service (HTTP POST)
  • (TipsTricks)用客户端模板精简JavaScript代码
  • (三)mysql_MYSQL(三)
  • (四)c52学习之旅-流水LED灯
  • (一)认识微服务
  • (转)EXC_BREAKPOINT僵尸错误
  • (转)shell中括号的特殊用法 linux if多条件判断
  • (转)程序员技术练级攻略
  • (转)关于多人操作数据的处理策略
  • .net 8 发布了,试下微软最近强推的MAUI
  • .net core webapi 大文件上传到wwwroot文件夹
  • .NET Core 网络数据采集 -- 使用AngleSharp做html解析
  • .NET Remoting Basic(10)-创建不同宿主的客户端与服务器端
  • .NET/C# 使窗口永不获得焦点
  • .考试倒计时43天!来提分啦!
  • ??在JSP中,java和JavaScript如何交互?
  • @param注解什么意思_9000字,通俗易懂的讲解下Java注解