您的位置:首页 > 职场人生

黑马程序员---线程间通讯

2015-09-29 15:21 232 查看
---------- android培训java培训、java学习型技术博客、期待与您交流! ------------

 

关键字:等待唤醒机制、Lock、Condition、停止线程、守护线程、join方法、优先级、yeild

 

线程间的通信

    之前的例子,卖票和存钱,不同线程运行的代码是一致的。线程间的通信解决的是,多个线程操作同一个资源,但是操作动作不同。所以需要多个run方法来存放数据。

    处理同一个资源的所有语句都需要同步,即使不在同一个类里或只有一句代码,多个线程意味着多个run方法,每个 run方法里都有同步才行。

    对于共享数据的操作可能有先后顺序,例如,输入、输出。但是具体执行时,可能是输入一段、输出一段,不能做到交替进行。可以通过在共享数据内加入判断语句来交替进行。

 

    等待唤醒机制

    线程运行时,内存中会建立“线程池”,等待线程都存在线程池中。等待唤醒机制使得多个线程可以交替进行。

    wait(),等待中,让线程进入线程池,但是执行资格仍在。且不能自己醒。会跑出异常,需要在函数内部处理。

notify唤醒的是线程池中的线程,通常唤醒第一个等待的线程。notifyAll();全部唤醒。

 

wait、notify、notifyaAll

都使用在同步中。因为要对持有锁的线程操作,所以要使用在同步中,因为同步才有锁。也就是说,这些方法必须标示所属线程使用的锁,例如r.wait(),意为持有r这个锁的线程等待。因为同步会出现嵌套,会有2个锁,不标示的话,不知道让哪个线程等待。同理适用于notify。

这些方法定义在Object类中的原因。因为这些方法在操作同步中的线程时,都必须要标识他们所操作线程的唯一锁。只有同一个锁上被等待线程,可以被同一个锁上另外的线程notify唤醒。也就是说,等待和唤醒必须是同一个锁的2个线程之间的行为。

因为锁可以是任意对象,所以这些方法可以被任意对象调用,所以这些方法都定义在了Object类中。

 

多个线程操作同一个数据,但是动作不同,可以使用等待唤醒机制,交替执行。例如,

public synchronized void set(String name)//保证线程会执行完代码后才放弃资格。

       {

              while(flag)//循环判断,使得线程不会轻易执行。

                     try{this.wait();}catch(Exception e){}

              this.name = name +"---"+ count++;

              System.out.println(Thread.currentThread().getName()+"---生产者"+this.name);

              flag = true;//实例变量,会记录数据。

              this.notifyAll();//前面都判断的话,可能会都等待,所以需要唤醒对象线程。因为循环判断,所以不用担心一起执行。

       }

 

       public synchronized void out()

       {

              while(!flag)//线程都在此处等待,唤醒后直接执行。需要使线程再次判断,所以用while。

                     try{this.wait();}catch(Exception e){}

              System.out.println(Thread.currentThread().getName()+"---消费者--"+this.name);

              flag = false;

              this.notifyAll();

       }

关键点是循环判断while(flag)和唤醒对象线程this.notifyAll();,主要是考虑线程较多,需要挨个交替执行。

 

JDK1.5提供了多线程升级解决方案,Lock接口和Condition接口。将同步synchronized替换为显式Lock操作(显式的锁机制);将Object中的wait、notify、notifyAll替换为Condition对象(显式的等待、唤醒操作机制),该对象可以对Lock锁进行获取。synchronized中对锁的操作,例如,获取锁、获取锁,都是隐式操作。lock没有新的功能,只是将对锁的操作公开化、显式,获取锁lock()和释放所unlock()。将等待、唤醒操作封装进Condition对象,使得一个锁可以对应多个对象。例如。

private Lock lock = new ReentrantLock();//新建锁的对象

private Condition condition_pro = lock.newCondition();//新建对象,封装锁的使用。

private Condition condition_con = lock.newCondition();

 

public void set(String name) throws InterruptedException

{

       lock.lock();

       try

       {

              while(flag)

                     condition_pro.await();

              this.name = name +"---"+ count++;

              System.out.println(Thread.currentThread().getName()+"---生产者"+this.name);

              flag = true;

              condition_con.signal();//只能唤醒condition_con,与下面对应。

       }

       finally

       {

              lock.lock();//释放锁的动作一定要执行。

       }

}

 

public void out() throws InterruptedException

{

       lock.lock();

       try

       {

              while(!flag)

                     condition_con.await();//只能让condition_con等待,与前面对应

              System.out.println(Thread.currentThread().getName()+"---消费者---"+this.name);

              flag = false;

              condition_pro.signal();

       }

       finally

       {

              lock.lock();

       }

}

 

优势在于,一个锁里可以绑定多个Condition对象。以前同步里面多个线程拥有一个锁,绑定一个对象,等待唤醒操作也只能由这个对象决定;现在锁可以绑定多个对象,进而有多个等待唤醒操作,操作更加灵活,只需要区分Condition对象即可。最大的优点是可以做到,本方只唤醒对方的操作

可以在嵌套中使用,不会出现死锁的情况。

锁需要绑定一个对象,这个对象可以是任意对象,可以和锁没有关系。现在,一个锁可以和多个对象绑定,也就意味着,一些线程和一个Condition产生联系,线程的等待唤醒由这个对象决定,例如,condition_pro.await();,condition_pro对象使得线程等待,那么无论在任何地方,只要condition_pro.signal()就能唤醒线程,一般都是唤醒对方的线程。

 

停止线程

原理:run方法结束。注意:是停止,不是暂停(冻结)。

1. 定义循环结束标记

多线程运行,代码一般都是循环结构,只要控制循环,就可以让run方法结束,也就是线程结束。一次性执行的话,单线程即可。

2. 使用interrupt(中断)方法

结束线程的冻结状态,使线程回到运行状态中。

还有stop方法,但1.5版本后不再使用,会报错。但是老版本有此方法。在线程还处于冻结状态,就将其终止,有安全隐患。

特殊情况

当现场处于冻结状态,就不会读取到标记,现场就不会结束,只能是暂停,只是丧失了运行资格而已。

中断状态不是停止状态,是冻结状态或暂停状态。interrupt方法,将处于冻结状态的线程强制的恢复到运行状态中来。只有恢复到运行状态,才能读取标记,使得run方法结束。

当没有指定方式让冻结的线程恢复到冻结状态时,需要对冻结状态进行清除,强制让线程恢复到运行状态中来。这样就可以通过操作标记让线程结束,线程运行完毕后自动结束。Thread类中提供了interrupt方法,满足该需求。

 

守护线程

守护线程,也叫用户线程,本质是后台线程。大部分线程都是前台线程。标为后台线程后,开启后和前台线程一样,抢劫CPU的运行权;当所有的前台线程都结束后,后台线程会自动结束,甚至无限循环也会结束。

特点:

1. 必须在守护线程前调用。

2. 所有前台线程都结束,只剩下守护线程时,守护线程结束,虚拟机退出,程序结束。

 

join方法

join方法,抢先获得CPU的执行权,先于主线程执行,例如,t1.join();。此时,主线程处于冻结状态,只有t1可以运行;只有t1运行结束,主线程才可以运行,等待让主线程让出执行权的线程死亡。一般用于临时加入线程。

join方法的位置会有影响,如果放在多个线程开启的下面,例如,t1.start();t2.start();t1.join();,t1和t2会交替执行,因为t1抢的是主线程的运行权,和t2无关。主线程依然在t1结束后运行,和t2的执行无关。

当A现场执行到了B线程的join方法时,A线程就会等待,等B线程都执行完,A线程才会执行。可以用来临时加入线程执行。可以嵌套使用。

如果t1等待,会造成主线程无法运行,所以使用interrupt方法来结束冻结状态,继续运行。因为是强制恢复运行,所以可能会出现异常,所以join方法会抛出InterruptedException异常。

开启线程的线程组,称为主线程组,里面的成员构成一个线程组。也可以通过ThreadGroup创建新的对象,封装进其他的组。很少用。

 

优先级:代表抢资源的频率,设置越高,执行越频繁,优先执行。所有线程的优先级,包括主线程,优先级默认设置为5,共有10级,数字越大,越优先执行。如果数字相差不大,优先程度几乎没有差别,只有1、5、10之间最明显,分别为min、norm、max。虽然可以设为最高级,也只是相对高而已,不可能只执行一个线程。

 

yield

多个线程运行时,临时强制释放线程的执行权,使得其他线程可以执行。可以减缓线程的运行,使得线程都有机会运行。

 

 

开发时如何编写线程

当某些代码需要同时运行时,用单独的线程进行封装。可以封装成类,或者对象。例如。

new Thread()//通过Thread类建立匿名类

{

       public void run()

       {

              for (int x=0;x<30;x++ )

              {

                     System.out.println(Thread.currentThread().getName()+"...Jobs..."+x);

              }

       }

}.start();

 

Runnable r = new Runnable()//通过Runnable接口建立匿名类对象

{

       public void run()

       {

              for (int x=0;x<30;x++ )

              {

                     System.out.println(Thread.currentThread().getName()+"...Intel..."+x);

              }

       }

};
new Thread(r).start();

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: