多线程(三)线程通信

线程中断、线程让步、线程睡眠、线程合并的使用推荐翻阅这篇博客:http://blog.csdn.net/ghsau/article/details/17560467

本文主要补充wait(),notify()和sleep()区别与联系以及为何不推荐使用stop(),suspend()

1. wait(),notify()

  wait(),notify()和notifyall()方法是java.lang.Object类为线程提供的用于实现线程间通信的同步控制方法。

线程等待有两种调用格式:
1.wait()等待通信线程唤醒后再继续执行本线程。
2.wait(long millis)等待通信线程唤醒或者最多等待millis毫秒后,再继续执行本线程。
调用wait()和notify()系列方法时,当前线程必须拥有此对象监视器(即对象锁)。如果当前线程不是此对象监视器的所有者,会抛IllegalMonitorStateException。
通过以下三种方法之一,线程可以成为对象监视器的所有者:
* 通过执行此对象的同步实例方法。
* 通过执行在此对象上进行同步的 synchronized 语句的正文。
* 对于 Class 类型的对象,可以通过执行该类的同步静态方法。
注意1:对于一个对象,某一时刻只能有一个线程拥有该对象的监视器。

package com.blog.spring.thread;

public class WaitTest {

    public static void main(String[] args) {
        WaitTest waitTest = new WaitTest();
        waitTest.startThread();
    }

    /**
     * 线程锁
     */
    private final Object object = new Object();

    /**
     * 启动线程
     */
    public  void startThread() {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始执行线程。。。");
                System.out.println("进入等待状态。。。");
                synchronized (object) {
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("线程结束。。。");
            }
        });

        t.start();
    }
}

上面的锁也可以用this代表当前对象,表示当前类的对象锁。
运行结果

开始执行线程。。。
进入等待状态。。。

  程序在未被唤醒之后,将不再打印“线程结束”,并且程序无法执行完毕一直处于等待状态。当前线程暂停执行,线程进入Blocked状态,cpu不会分给其时间,等待其他线程执行notify()方法或者notifyall()方法。也可以用interrupt()取消.注意:线程调用interrupt(),则线程会取消等待状态,其中断状态将被清除,它还将收到一个 InterruptedException。 我们可以捕获该异常,并且做一些处理详细参考《线程中断》
它还有种调用格式:wait(long millis)等待通信线程唤醒或者最多等待millis毫秒后,再继续执行本线程。

注意:notify()方法或者notifyall()方法是用来唤醒其它线程的,线程处于等待后,不能执行任何方法,只能由其它线程唤醒或者当前线程被中断。

中断示例代码如下

public class InterruptTest {

    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread("MyThread");
        t.start();
        //线程中断
        t.interrupt();

    }

    static class MyThread extends Thread {
        int i = 0;
        public MyThread(String name) {
            super(name);
        }
        public void run() {
            synchronized (this){
                System.out.println("线程开始等待");
                try {
                    this.wait();
                    System.out.println("线程已经被中断,该信息无法被打印");
                } catch (InterruptedException e) {
                    System.out.println("捕获到中断异常");
                }

                System.out.println("线程等待状态被取消");

                if(isInterrupted()){//如果线程被标记为中断状态
                    System.out.println("线程被中断");
                }
            }
        }
    }
}

不调用中断方法运行结果:

线程开始等待

调用线程中断方法运行结果:

线程开始等待
捕获到中断异常
线程等待状态被取消

Process finished with exit code 0

  当一个线程处于wait()状态时,也即等待它之前所持有的object’s monitor被释放,通过notify()方法可以让该线程重新处于活动状态,从而去抢夺object’s monitor,唤醒该线程。如果多个线程同时处于等待状态,那么调用notify()方法只能随机唤醒一个线程。在同一时间内,只有一个线程能够获得object’s monitor,执行完毕之后,则再将其释放供其它线程抢占。
那么顾名思义,notifyAll()就是用来唤醒正在等待状态中的所有线程的,不过也需要注意以下几点:
(1)notifyAll()只会唤醒那些等待抢占指定object’s monitor的线程,其他线程则不会被唤醒。
(2)notifyAll()只会一个一个的唤醒,而并非统一唤醒。因为在同一时间内,只有一个线程能够持有object’s monitor
(3)notifyAll()只是随机的唤醒线程,并非有序唤醒。

思考:怎么做到有序唤醒,大家可以思考下怎么实现?

2. wait(),sleep()的区别

sleep()方法来自于Thread类,从源码给出的解释来看,sleep()方法可以做到如下几点:
(1)首先,调用sleep()之后,会引起当前执行的线程进入暂时中断状态,也即睡眠状态。
(2)其次,虽然当前线程进入了睡眠状态,但是依然持有monitor对象。
(3)在中断完成之后,自动进入唤醒状态从而继续执行代码。

那么从以上的理论和实践来分析,我们能得出如下结论:
(1)在线程的运行过程中,调用该线程持有monitor对象的wait()方法时,该线程首先会进入等待状态,并将自己持有的monitor对象释放。
(2)如果一个线程正处于等待状态时,那么唤醒它的办法就是开启一个新的线程,通过notify()或者notifyAll()的方式去唤醒或者等待时间结束或者中断。当然,需要注意的一点就是,唤醒方法调用必须是同一个monitor对象。
(3)sleep()方法虽然会使线程中断,但是不会将自己的monitor对象释放,在中断结束后,依然能够保持代码继续执行。

3. stop(),suspend()不好的原因

  stop()方法作为一种粗暴的线程终止行为,在线程终止之前没有对其做任何的清除操作,因此具有固有的不安全性。 用Thread.stop()方法来终止线程将会释放该线程对象已经锁定的所有监视器。如果以前受这些监视器保护的任何对象都处于不连贯状态,那么损坏的对象对其他线程可见,这有可能导致不安全的操作。 由于上述原因,因此不应该使用stop()方法,而应该在自己的Thread类中置入一个标志,用于控制目标线程是活动还是停止。如果该标志指示它要停止运行,可使其结束run()方法。如果目标线程等待很长时间,则应使用interrupt()方法来中断该等待。
  suspend()方法 该方法已经遭到反对,因为它具有固有的死锁倾向。调用suspend()方法的时候,目标线程会停下来。如果目标线程挂起时在保护关键系统资源的监视器上保持有锁,则在目标线程重新开始以前,其他线程都不能访问该资源。除非被挂起的线程恢复运行。对任何其他线程来说,如果想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。由于上述原因,因此不应该使用suspend()方法,而应在自己的thread类中置入一个标志,用于控制线程是活动还是挂起。如果标志指出线程应该挂起,那么用wait()方法命令其进入等待状态。如果标志指出线程应当恢复,那么用notify()方法重新启动线程。

对线程合并的补充解释:

public class JoinTest {

    public static void main(String[] args) throws InterruptedException {
        JoinThread t1 = new JoinThread("t1");
        JoinThread t2 = new JoinThread("t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("主线程开始执行!");
    }
}
    class JoinThread extends Thread {
    public JoinThread(String name) {
        super(name);
    }
    public void run() {
        for(int i = 1; i <= 10; i++)
            System.out.println(getName()+":"+getId() + "执行了" + i + "次");
    }
}

调用t1.join();t2.join();程序的运行结果:

t1:11执行了1次
t2:12执行了1次
t1:11执行了2次
t2:12执行了2次
t1:11执行了3次
t2:12执行了3次
t1:11执行了4次
t1:11执行了5次
t2:12执行了4次
t1:11执行了6次
t2:12执行了5次
t2:12执行了6次
t2:12执行了7次
t1:11执行了7次
t2:12执行了8次
t1:11执行了8次
t2:12执行了9次
t2:12执行了10次
t1:11执行了9次
t1:11执行了10次
主线程开始执行!

不调用t1.join();t2.join();程序的运行结果:

主线程开始执行!
t1:11执行了1次
t2:12执行了1次
t1:11执行了2次
t2:12执行了2次
t2:12执行了3次
t1:11执行了3次
t1:11执行了4次
t1:11执行了5次
t1:11执行了6次
t1:11执行了7次
t2:12执行了4次
t2:12执行了5次
t2:12执行了6次
t2:12执行了7次
t2:12执行了8次
t2:12执行了9次
t2:12执行了10次
t1:11执行了8次
t1:11执行了9次
t1:11执行了10次

相信以上运行结果能够帮助大家更明白线程合并!

线程通信还有更高级有效的方式:Condition,我放在 多线程(五)线程同步(中)-Lock,Condition, ReadWriteLock
欢迎大家去踩踩!

参考文章:
(http://blog.csdn.net/kaka534/article/details/51849285)
(http://blog.csdn.net/ghsau/article/details/17560467)
http://blog.csdn.net/hudashi/article/details/7001070
http://blog.sina.com.cn/s/blog_687c844d0101ietn.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值