java多线程间通信图解实例

java多线程间通信图解实例

目录(?)[-]

  1. 总结
    1. 多线程间通信
    2. 解决多线程通信安全问题
    3. 等待唤醒机制
    4. 生产者消费者例子
    5. 停止线程
    6. 守护线程

      总结

      今天主要学习了多线程间的通信问题。

      1.     多线程间通信

      既多个线程在操作同一个资源,但是操作的动作不同。

       

      Figure 1

      如图,input和output在同时操作同一个资源,但是他们所做的的动作并不同。

      2.     解决多线程通信安全问题

      当多个线程不同方法操作同一资源时,会出现数据错乱问题,这就涉及到了

      多线程间通信的安全问题。

      出现安全问题要用同步,加锁,但是这里不能直接将run方法同步,这样会导致其成为单线程。

       

      Figure2

             要解决此问题,需要将synchronized定义在run方法内部,只需要将线程操作代码同步即可。示例中(代码省略)有input、output线程,因此需要将其各自内部的操作共同资源的代码同步。(虽然他们(线程操作代码)在两个run方法里面,但是它们在操作同一个资源)。

      要用同步解决这类问题,必须满足两个前提:

      l  明确是否是多线程;

      l  明确是否使用同一个锁。

       


      1、

      Output类中的run方法里有操作共同资源的代码,因此要对其进行同步

       

      Figure3

           Input类中的run方法里也有操作共同资源的代码,因此也要进行同步。

       

      Figure4

      但是,运行后发现仍然出现数据错乱。

      此时需要考虑是否使用同一个锁

      2、

      使用唯一对象。

      可以是某唯一的对象,XXX.class(较牵强)

      建议使用这里共同操作的唯一资源,——r

       

      Figure5

      3、数据私有化(private),只对外提供方法

      Figure6

       

       


      3.     等待唤醒机制

       

       

      Figure7

      操作线程的方法:wait、notify、notifyAll——都在Object类中。

      监视器(也就是锁),只有同步才会有锁,所以这些方法都用于同步当中。

      因为要对持有监视器(锁)的线程操作。所以要使用在同步中(只有同步才具有锁)。

      由于这些方法在操作同步线程时,都必须要标识他们所操作线程持有的锁,只有同一个锁上的被等待线程,才可以被同一个锁上notify唤醒;不可以对不同锁中的线程进行唤醒,所以这些操作线程的方法要定义在Object类中。

      也就是说,等待和唤醒必须是同一个锁。

      锁可以是任意对象,可以被任意对象调用的方法定义在Object类中(为什么wait、notify、notifyAll都定义在Object类中)。

      线程运行时,内存中会建立一个线程池,等待线程都存放在其中,notify唤醒线程池中的线程。notify通常唤醒第一个被等待的线程。

      4.     生产者消费者例子

      Figure8

      if只判断一次;while,每次醒来都判断

      While—–

      Notifyall—-

        为什么要定义while判断标记?

      让被唤醒的线程再一次判断标记。

      为什么要定义notifyAll()?

      为了唤醒对方线程。如果只用notify(),容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待——wait().

       

      Figure9

      JDK升级后提供了新的包(packet):Java.util.concurrent.locks(使用Lock之前一定要导入此包)

      提供了多线程升级解决方案。未升级之前,一个锁只对应一个wait、notify。

      将同步synchronized替换成实现Lock。

      将Object中的wait、notify、notifyAll替换成了condition对象。该对象可以通过Lock锁进行获取。

      Public interface Lock—->Lock提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的Condition对象。

      condition将Object监视器方法(wait/notify/notifyAll)分解成截然不同的对象,以便通过将这些对象与任意的Lock实现组合使用,为每个对象提供了多个等待。

        Lock替代了synchronized方法和语句的使用。

        Condition替代了Object监视器方法的使用

      Figure10

       privateLock lock = new ReentrantLock();

      private Condition con = lock.newCondition();

      Reentrant:可重入;函数可以由多于一个线程并发使用,而不必担心数据错误。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。

      ReentrantLock(重入锁):是一种递归无阻塞的同步机制。

       

      Figure11

      获取锁lock.lock();————void lock()____获取锁,如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直出于休眠状态。

      释放锁lock.unlock();————void unlock()____

      Figure12

       condition.await();等待————void await()

      condition.signal();唤醒————void signal()

      Void signalAll()唤醒所有等待线程

       

      释放锁的动作一定要执行(finally中的内容一定会执行)

      finally { } 里存放的一般是释放资源动作

      Figure13

      Figure14

      和notifyAll的道理一样,也要使用signalAll()唤醒(且唤醒包含对方)。仅仅是将原来的代码wait、notifyAll替换为了lock、signalAll。

      新特性好处:一个锁上可以有多个相关的Condition对象。

      升级后,可以定义两个对象。就可以在set()、out()两个方法中调用这两个对象的await、signal方法,来彼此交替唤醒。

      Figure15

      PrivateCondition condition_pro = lock.newCondition();

      Private Conditioncondition_con = lock newCondition();

      Public void set(Stringname)throws InterruptedException

      {

      Lock.lock();

      Try{

      While(flag)

      Condition_pro.await();(生产者等待)

      ——-;

      Flag = true;

      Condition_con.signal();(唤醒消费者)

      }

      Finally{

      Lock.unlock();

      }

      }

      Public void out()

      {

      Lock.lock();

      Try{

      While(!flag)(先判断是否满足标记){

      Condition_con.await();

      ——–;

      Flag = false;

      Condition_pro.signal();

      }

      }

      Finally{

      Lock.unlock();

      }

      }

      互相唤醒(只唤醒对方)。

      5.     停止线程

      Stop方法已经过时。有bug,但是没有在api中删除,因为有老程序员在用。

      l  如何停止线程:run方法结束(唯一的方法)run方法结束,线程就结束。

      开启多线程运行,运行代码通常是循环结构。控制住循环,就可以让run方法结束,线程也就结束(最终原理)。

      l  当线程处于了冻结(中断)状态,就不会读取到标记,线程就不会结束。(中断不是停止)

      Public void interrupt();

      Interrupt方法将处于冻结状态的线程强制恢复到运行状态中来,

      强制清除冻结状态

      Figure 16

      当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。

      强制让线程恢复到运行状态中来,这样就可以操作标记线程结束。

      Thread类提供该方法interrupt();——sleep、wait、join都能被中断。

      6.     守护线程

      Public final void        setDaemon(boolean on)

      将该线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,jvm退出。(该方法必须在启动线程前调用

      Figure 17

       

      两个线程都是守护线程时:

      可以看到java虚拟机退出。

      Figure 18

      当所有的前台线程都结束后,后台自动结束运行。

本文链接地址: java多线程间通信图解实例