java多线程同步,等待,唤醒
notify()、notifyAll()、wait()属于java.lang.Object,java.lang.Thread也是Object,自然也有上述方法;
sleep()、interrupt()、interrupted()、join()、yield()属于java.lang.Thread
wait()方法表示,放弃当前对资源的占有权,等啊等啊,一直等到有人通知我,我才会运行后面的代码。
notify()方法表示,当前的线程已经放弃对资源的占有,通知等待的线程来获得对资源的占有权,但是只有一个线程能够从wait状态中恢复,然后继续运行wait()后面的语句;
notifyAll()方法表示,当前的线程已经放弃对资源的占有,通知所有的等待线程从wait()方法后的语句开始运行。
Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。
单单在概念上理解清楚了还不够,需要在实际的例子中进行测试才能更好的理解。对Object.wait(),Object.notify()的应用最经典的例子,应该是三线程打印ABC的问题了吧,这是一道比较经典的面试题,题目要求如下:
建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:
package thread; public class MyThreadPrinterABC implements Runnable { private String name; private Object prev; private Object self; private MyThreadPrinterABC(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 { if (count > 0) 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(); MyThreadPrinterABC pa = new MyThreadPrinterABC("A", c, a); MyThreadPrinterABC pb = new MyThreadPrinterABC("B", a, b); MyThreadPrinterABC pc = new MyThreadPrinterABC("C", b, c); new Thread(pa).start(); Thread.sleep(1); new Thread(pb).start(); Thread.sleep(1); new Thread(pc).start(); Thread.sleep(1); } }
下面继续说sleep()、join()、yield()
sleep()使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据。注意该方法要捕捉异常。
例如有两个线程同时执行(没有synchronized)一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完毕后,低优先级的线程才能够执行;但是高优先级的线程sleep(500)后,低优先级就有机会执行了。
总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
join()方法使调用该方法的线程在此之前执行完毕,也就是等待该方法的线程执行完毕后再往下继续执行。注意该方法也需要捕捉异常。
yield()方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。
A.join,在API中的解释是,堵塞当前线程B,直到A执行完毕并死掉,再执行B;A.yield,A让出位置,给B执行,B执行结束A再执行。跟join意思正好相反!
sleep 方法使当前运行中的线程睡眠一段时间,进入不可以运行状态,这段时间的长短是由程序设定的,yield方法使当前线程让出CPU占有权,但让出的时间是不可设定的。
yield()也不会释放锁标志。
实际上,yield()方法对应了如下操作;先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把CPU的占有权交给次线程,否则继续运行原来的线程,所以yield()方法称为“退让”,它把运行机会让给了同等级的其他线程。
sleep 方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,所以不可能让出较低优先级的线程此时获取CPU占有权。在一个运行系统中,如果较高优先级的线程没有调用sleep方法,也没有受到I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,方可有机会运行。
yield()只是使当前线程重新回到可执行状态,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行,所以yield()方法只能使同优先级的线程有执行的机会。
最后说一下interrupt和interrupted,直接看例子:
package thread; public class TestInterrupt implements Runnable { int i = 10; @Override public void run() { while (i-- > 0) { System.out.println("I am running!"); try { Thread.sleep(100); } catch (InterruptedException e) { System.out.println("--------- InterruptedException ----------"); continue; } } } public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new TestInterrupt()); t.start(); Thread.sleep(500);// 运行一断时间后中断线程 System.out.println("****************************"); System.out.println("Interrupted Thread!"); System.out.println("****************************"); t.interrupt();//你以为到这里会中断吗? } }
I am running! I am running! I am running! I am running! I am running! I am running! **************************** Interrupted Thread! **************************** --------- InterruptedException ---------- I am running! I am running! I am running! I am running!
虽然中断发生了,但线程仍然在进行,离开线程有两种常用的方法:
抛出InterruptedException和用Thread.interrupted()检查是否发生中断,下面分别看一下这两种方法:
1.在阻塞操作时如Thread.sleep()时被中断会抛出InterruptedException(注意,进行不能中断的IO操作而阻塞和要获得对象的锁调用对象的synchronized方法而阻塞时不会抛出InterruptedException),代码如下:
public void run() { //死循环执行打印"I am running!" try { while (true) { System.out.println("I am running!"); //休眠一断时间,中断时会抛出InterruptedException Thread.sleep(50); } } catch (InterruptedException e) { System.out.println("TestInterrupt.run() interrupted!"); } }
Thread.interrupted()检查是否发生中断.Thread.interrupted()能告诉你线程是否发生中断,并将清除中断状态标记,所以程序不会两次通知你线程发生了中断.代码如下:
public void run() { //检查程序是否发生中断 while (!Thread.interrupted()) { System.out.println("I am running!"); } System.out.println("TestInterrupt.run() interrupted!"); }