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!");    
   }    

 

posted on 2016-11-30 13:39  张玉龙  阅读(582)  评论(0编辑  收藏  举报