Java多线程
来源:互联网 发布:魔力学堂源码 编辑:程序博客网 时间:2024/06/16 09:24
这几个方法都是Object的方法,主要配合synchronized同步一起使用。这涉及到Java的锁机制,可以参考我的一篇博文 Java多线程 - 锁机制。
我们接下来看看这几个方法:
(1)wait
public final void wait() throws InterruptedException,IllegalMonitorStateException
线程要调用Object实例对象的wait方法前,必须要先获取该Object实例对象锁,否则报IllegalMonitorStateException异常,所以wait方法必须在获取Object实例对象锁的同步块中调用。在线程调用Object实例对象的wait方法后,线程便进入“等待”状态,直到收到通知或者中断为止。
// ① wait 实质在线程(如A)调用Object实例(如lockA)的wait方法后,线程(A)会进入实例对象(lockA)的等待池中,而等待池的线程处于wait状态,不会主动去获取Object实例(lockA)对象锁,等待notify或者notifyAll。所以线程(A)等待的不是Object实例(lockA)对象锁,等待的是有其他线程调用Object实例(lockA)的notify或者notifyAll通知。
例子:
public static class RunnableA implements Runnable { private Object lockA; public RunnableA(Object lockA) { this.lockA = lockA; } @Override public void run() { synchronized (lockA) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.err.println("RunnableA before wait lockA"); try { // 必须在 synchronized (lockA) {} 内调用,线程会在这里阻塞,等待通知 lockA.wait(); } catch (InterruptedException e) { e.printStackTrace(); } // 在其他线程没有调用notify或者notifyAll方法之前,不会执行 System.err.println("RunnableA after wait lockA"); } } }
(2)notify
public final native void notify() throws IllegalMonitorStateException
和wait方法一样,调用之前一定要先获取Object实例对象锁,否则报IllegalMonitorStateException错误。
在线程调用Object实例对象notify方法后,会通知等待该Object实例对象锁的其他线程(也就是调用过该Object实例的wait方法处于等待的线程),获取到该Object对象锁的线程回继续执行wait方法后面的语句。
需要注意的是,在线程(A)调用Object实例的notify方法后,并不会立刻释放Object实例对象锁,synchronized同步块的代码会执行完了(跳出synchronized同步块)之后,线程(A)才会释放Object实例对象锁,线程(B,任意一个处于wait状态的线程)才能获取到Object实例对象锁,继续往下执行。
如果有多个线程(A,B,C)都处于wait状态,任意一个线程(如A)会收到通知,其他线程(B,C)的仍然处于wait状态。即使Object实例对象锁闲置了,其他线程(B,C)也不会主动改变状态,也不会主动去获取Object实例对象锁。除非有线程(A)调用了Object实例notify方法,wait的线程(B,C)才机会收到通知,继续执行。
// ② notify 实质 // 结合 ① wait 实质在线程(如B)调用Object实例(lockA)的notify方法后,收到通知的线程(A)会被移到实例对象(lockA)的锁池,在这个池的线程会竞争实例对象(lockA)锁。竞争到锁的线程也会被移出锁池,继续执行wait后面的语句。
例子:
public static class RunnableB implements Runnable { private Object lockA; public RunnableB(Object lockA) { this.lockA = lockA; } @Override public void run() { synchronized (lockA) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.err.println("RunnableB notify lockA"); // 必须在 synchronized (lockA) {} 内调用 lockA.notify(); // 即使调用notify通知其他线程,这段代码还是会先执行,因为还没有释放lockA锁 System.err.println("RunnableB after notify lockA"); } }}
在给出main方法:
public static void main(String[] args) { // 实例对象 Object lockA = new Object(); // RunnableA和RunnableB持有同一个实例对象 Thread threadA = new Thread(new RunnableA(lockA)); Thread threadB = new Thread(new RunnableB(lockA)); // 启动threadA和threadB threadA.start(); threadB.start();}
打印结果:
RunnableA before wait lockA // RunnableA开始等待,并释放lockA实例对象锁RunnableB notify lockA // RunnableB执行RunnableB after notify lockA // RunnableB继续执行,释放lockA实例对象锁RunnableA after wait lockA // RunnableA收到通知,获取到lockA实例对象锁,继续执行wait后面的语句
(3)notifyAll
public final native void notifyAll() throws IllegalMonitorStateException
和wait,notify方法一样,调用之前一定要先获取Object实例对象锁,否则报IllegalMonitorStateException错误。
在线程调用Object实例对象notifyAll方法后,会通知等待该Object实例对象锁的所有线程(也就是调用过该Object实例的wait方法处于等待的所有线程)。
根据notify的实质原理,有2个线程(A,B)调用了Object实例对象(lockA)的wait方法,此时,这2个线程(A,B)就被移到了Object实例对象(lockA)的等待池中。有个线程(C)调用了Object实例对象(lockA)的notifyAll方法,此时,处于wait状态的2个线程(A,B)都会被移到Object实例对象(lockA)的锁池中。那么这2个线程(A,B)就会开始竞争Object实例对象(lockA)锁。如果其中一个线程(A)争到了,执行wait后面的语句,跳出synchronized同步代码块,释放锁。此时的另一个线程(B)还在Object实例对象(lockA)的锁池中,因为Object实例对象(lockA)锁已经闲置了,线程(B)便可以获取Object实例对象(lockA)锁,也接着执行wait后面的语句。
所以在线程中调用Object实例对象的notifyAll方法,会让所有处于wait状态的线程都有机会竞争实例对象锁。
(4)wait(long)和wait(long,int)
这两个方法是设置等待超时时间的,后者在超值时间上加上ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从wait(long)返回。另外,需要知道,如果设置了超时时间,当wait()返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为wait()方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在notify之前改变标志位的值,在wait()方法后读取该标志位的值来判断,当然为了保证notify不被遗漏,我们还需要另外一个标志位来循环判断是否调用wait()方法。
(5)sleep和wait的区别
两者都可以让线程阻塞,但是有2个明显的区别:
sleep是Thread类的方法,而wait是Object类的方法,根本不是同一类的方法。
sleep方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep方法的过程中,线程不会释放对象锁。而当调用wait方法的时候,线程会放弃对象锁,进入等待此对象的等待池,只有针对此对象调用notify方法后本线程才进入对象锁池准备。
(6)巩固
下面再给出一个例子(网上搜的),巩固一下我们对wait和notify的使用:
public class MyThreadPrinter implements Runnable { private String name; private Object prev; private Object self; private MyThreadPrinter(String name, Object prev, Object self) { this.name = name; this.prev = prev; this.self = self; } @Override public void run() { int count = 10; while (count > 0) { synchronized (prev) { synchronized (self) { System.out.print(name); count--; self.notify(); } try { prev.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws Exception { Object a = new Object(); Object b = new Object(); Object c = new Object(); MyThreadPrinter pa = new MyThreadPrinter("A", c, a); MyThreadPrinter pb = new MyThreadPrinter("B", a, b); MyThreadPrinter pc = new MyThreadPrinter("C", b, c); new Thread(pa).start(); Thread.sleep(100); //确保按顺序A、B、C执行 new Thread(pb).start(); Thread.sleep(100); new Thread(pc).start(); Thread.sleep(100); } }
我们来分析一下上面的例子。
(1)pa(启动或唤醒),先获取prev(c)对象锁(同时持有c对象锁是pc线程。第一次获取时,pc没有启动;第一次之后,pc早已经执行完synchronized(self)代码块,自然已经释放了c对象锁),然后获取self(a)对象锁(同时持有a对象锁是pb线程。第一次获取时,pb没有启动;第一次之后,pb调用a的wait方法,处于阻塞状态,释放了a对象锁),再调用self(a)的notify,唤醒pb(第一次启动时,pb并没有启动;第一次之后,pb处于wait状态,便收到通知,开始唤醒),最后调用prev(c)的wait方法,开始阻塞,等待prev(c)的notify或者notifyAll。
(2)pb(启动或唤醒),先获取prev(a)对象锁(同时持有a对象锁是pa线程。此时的pa处于wait状态,而且已经执行完synchronized(self)代码块,不再持有a对象锁),然后再获取self(b)对象锁(同时持有a对象锁是pc线程。第一次获取时,pc没有启动;第一次之后,pc调用b的wait方法,处于阻塞状态,释放b对象锁),再调用self(b)的notify,唤醒pc(第一次启动时,pc并没有启动;第一次之后,pc处于wait状态,便收到通知,开始唤醒),最后调用prev(a)的wait方法,开始阻塞,等待prev(a)的notify或者notifyAll。
(3)pc(启动或唤醒),先获取prev(b)对象锁(同时持有b对象锁是pb线程。此时的pb处于wait状态,而且已经执行完synchronized(self)代码块,不再持有b对象锁),然后再获取self(c)对象锁(同时持有c对象锁是pa线程。pa调用c的wait方法,处于阻塞状态,释放c对象锁),再调用self(c)的notify,唤醒pa(pa处于wait状态,便收到通知,开始唤醒),最后调用prev(b)的wait方法,开始阻塞,等待prev(b)的notify或者notifyAll。因为pa收到唤醒通知,有回到(1)。
所以运行的结果是ABCABCABCABC…
到这里,相信你也会很自如的使用wait和notify了。
参考资料
【Java并发编程】之十:使用wait/notify/notifyAll实现线程间通信的几点重要说明
- 【Java多线程】多线程死锁
- Java 多线程
- java 多线程
- java多线程
- JAVA多线程
- java多线程
- JAVA多线程
- java多线程
- JAVA 多线程
- Java多线程
- java多线程
- JAVA 多线程
- Java 多线程
- Java 多线程
- java多线程
- Java 多线程
- Java多线程
- java 多线程
- UVa 12895
- JAVA中堆和栈的区别
- 替换桌面快捷方式图标
- ListView详解
- OPENCV 遍历文件夹中的图片方法
- Java多线程
- [Motion]MPU9250的基本框架
- C# 属性的代码生成失败。错误是无法将属性转换为InstanceDescriptor
- 快速入门微信硬件jsapi教程全集
- PCIE 设备扫描的过程
- java中stringBuilder的用法
- nodejs优缺点
- 关于开发webStorm遇到的问题总结
- ES6/7/8新特性Promise,async,await,fetch带我们逃离异步回调的深渊