天天看点

【多线程核心技术】---线程通信

一:等待/通知机制

    线程与线程之间不是独立的个体,它们之间可以互相通信和协作。

   1: 不需要等待/通知机制实现线程间的通信:

   2:等待/通知机制

        多个线程之间可以实现通信,原因是多个线程共同访问同一个变量,但这种机制不是“等待/通知”,两个线程完全是主动地去读取一个共享变量,在花费读取时间的基础上,读到的值不是想要的,不能完全确定。所以需要一种“等待/通知”机制来满足需求。

   3:等待/通知机制的实现

    方法wait()的作用是使当前执行代码的线程进行等待,wait()是Object类的方法,该方法用来将当前线程置于“预执行队列”中,并且wait()所在的代码行处停止执行 ,直到通知被中断为止。

    在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步块中调用wait()方法。

    在执行wait()方法后,当前线程释放锁,抛出IllegalMonitorStateException,不需try-catch语句进行捕获异常。

    方法notify()也需在同步方法或者同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果在调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。

    该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑出其中一个呈wait状态的线程,对其发出通知notify,并使其等待获取该对象的对象锁。

    执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也也并不能马上获取该对象锁,要等到notify()方法的线程将程序执行完,也就是出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。当第一次获得该对象锁的wait线程运行完毕以后,它会释放该对象锁,如果此时该对象没有再次使用notify语句,则即该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。(即notify方法一次只能唤醒一个锁)

    wait使线程停止运行,而notify使停止的线程继续执行。

    关键字synchronized可以将任何一个Object对象作为同步对象来看待,Java为每个对象都实现了wait()和notify()方法,它们必须用在被synchronized同步的Object的临界区内。

    wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。

    notify()方法可以随机唤醒等待队列中等待同一共享资源的“一个”线程”,并使该线程退出等待队列,进入可运行状态,这就是notify()方法仅通知“一个”线程。

    notifyAll()方法可以使所有正在等待队列中等待同一资源的“全部”线程从等待状态退出,进入可运行状态。优先级高的那个线程最先执行,但也有可能随机执行,则取决于JVM虚拟机。

【多线程核心技术】---线程通信
【多线程核心技术】---线程通信

4:方法wait()锁释放与notify()锁不释放

    当方法wait()被执行后,锁被自动释放,但执行完notify()方法,锁却不会马上释放。

    sleep()方法不释放锁。

    当前对象线程(Lock)在同步代码块中执行notify(),线程对象(Lock)不会马上释放锁,而是必须执行完notify()方法所在的同步synchronized代码块后才释放锁。

5:当interrupt方法遇到wait()方法

    当线程呈wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常。

但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。

如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。

需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到wait()/sleep()/join()后,就会立刻抛出InterruptedException。

    Thread.interrupt() VS Thread.stop()

  这两个方法最大的区别在于:interrupt()方法是设置线程的中断状态,让用户自己选择时间地点去结束线程;而stop()方法会在代码的运行处直接抛出一个ThreadDeath错误,这是一个java.lang.Error的子类。所以直接使用stop()方法就有可能造成对象的不一致性。

6:只通知一个线程

    方法notify()仅随机唤醒一个线程,

    在多次调用notify()方法时,会随机将等待wait状态的线程进行唤醒。多次调用notify()方法会唤醒全部WAITING中的线程

7:唤醒全部线程

    notifyAll()方法会唤醒全部线程。

8:方法wait(long)的使用

    带参数的wait(long)方法的功能是将等待某一时间内是否有线程对锁进行唤醒,如果超时则自动唤醒。

9:通知过早

   如果先通知notify()方法 thread.sleep(),则wait()方法后的代码即不会被执行。(等待被唤醒---》方法wait永远不会被通知)或者notify()方法改变了对象的值,使其wait()方法的同步代码块不符合条件,则不执行。

   如果先wait()方法 thread.sleep(),则notify()方法执行完synchronized代码块后的代码,再去执行wait()后的代码。

10:等待wait的条件发生变化

    在使用wait()/notify()模式时,如果wait()的条件发生变化,则会造成程序逻辑的混乱。

    用while()只要条件不成立就不会执行,少用 i f { }判断。

     if 与 while判断条件的合理使用

11:生产者/消费者模式实现

    等待/通知模式最经典案例就是“生产者/消费者”模式。其原理都是基于wait/notify。

    1)一生产与一消费:操作值

        创建两个线程对象,一个生产者线程,一个消费者线程。

    2)多生产与多消费:操作值---假死

        线程全部进入WAITING状态,则程序就不会再执行业务功能了,整个项目呈停止状态。即“假死”。

        呈假死状态的进程中所有的线程都呈WAITING状态。

        代码中通过wait/notify进行通信,但不能保证notify唤醒的是异类或者同类。则线程不能继续运行下去,大家都在等待,则呈WAITING状态。

    3)多生产与多消费者:操作值

        notify()改为notifyAll()通知所有类,防止“假死”状态。

    4)一生产与一消费:操作栈

        生产者使用堆栈List对象中放入数据,消费者从List堆栈中取出数据,List容量为1。

    5)一生产与多消费---操作栈:解决wait条件改变与假死

         使用一个生产者向堆栈List对象中放入数据,而多个消费者从List堆栈中取出数据,List容量为1。

    6)多生产与一消费:操作栈

        使用生产者向堆栈List对象中放入数据,而多个消费者从List堆栈中取出数据,List容量为1。

    7)多生产与多消费:操作栈

         使用生产者向堆栈List对象中放入数据,而多个消费者从List堆栈中取出数据,List容量为1。

12:通过通道进行线程间的通信:字节流

    管道流:用在不同线程间直接传输数据,实现不同线程间的通信。

        1)PipedInputStream和PipedOutputStream

              PipedInputStream   inputStream=new PipedInputStream()

              PipedOutputStream   outputStream=new PipedOutputStream()

        读取线程new ThreadRead(outputStream)启动。使用inputStream。connect(outputStream)或outputStream(inputStream)实现两个Stream之间的通信连接。

13:通过通道进行线程间的通信:字符流

       1)PipedReader和PipedWriter

14:等待/通知之交叉备份

    原理:volatile private Boolean prevIsA=false;

二:方法join的使用

     很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往早于子线程结束。如果主线程需要子线程的数据则需要join()方法。     方法join的作用是使所属的线程对象 x 正常执行run()方法中的任务,而使当前线程 z 进行无限制的阻塞,等待线程 x 销毁后再继续执行 z 后面的代码。

    方法join()的作用是等待线程对象的销毁。方法join具有使线程排队运行的作用,类似于同步的运行效果。

    join与synchronized的区别:

    join在内部使用wait()方法进行等待,而synchronized关键字使用的是“对象监视器”原理作为同步。

    在join过程中,如果当前线程对象被中断,则当前线程出现异常,

    方法join(long)设定等待的时间

    join()与sleep()方法对同步处理上的区别:

    方法join(long)的功能在内部使用wait(long)方法来实现的,所以方法join()方法具有释放锁的特点。而sleep(long)方法不释放锁。

    方法join()后面的代码提前运行:出现意外。

三:类ThreadLocal的使用

    public static 变量的形式可以实现变量值的共享, 所有的线程可以使用同一个public static 变量。

    类ThreadLocal实现每一个线程都有自己的共享变量。

    类ThreadLocal主要解决的是使每个线程绑定自己的值,可以将ThreadLocal类比喻为全局存放数据的盒子,盒子中可以存储每个线程的私有数据。

    类ThradLocal解决的是变量在不同线程间的隔离性,也就是不同线程拥有自己的值。及使每个线程都有自己的私有属性。

    get()返回null 问题———》让实现类继承ThreadLocal类,并覆盖initialValue()方法,使其方法具有初始值,返回初始值。

    子线程和父线程各有各自所拥有的的值,不存在继承关系。

四:类InheritableThreadLocal的使用

    使用类 InheritableThreadLocal可以在子线程中取得父线程继承下来的值。

 注意:如果使用InheritableThreadLocal类,字线程在取得值的同时,父线程将InheritableThreadLocal中的值进行更改,那么子线程的值还是旧值不会改变。

以下文章来自:http://blog.csdn.net/linxdcn/article/details/72819817?fps=1&locationNum=13

线程状态

public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
           
  • NEW 状态是指线程刚创建, 尚未启动
  • RUNNABLE 状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等
  • BLOCKED 这个状态下, 是在多个线程有同步操作的场景, 这个事件将在另一个线程放弃了这个锁的时候发生,也就是这里是线程在等待进入临界区
  • WAITING(无线等待) 这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在临界点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束
  • TIMED_WAITING 这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态
  • TERMINATED 这个状态下表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)

java层次的状态转换图

【多线程核心技术】---线程通信

操作系统层次的状态转换图

【多线程核心技术】---线程通信

(2)wait()和notify/notifyAll()方法

wait()方法

  • 线程进入WAITING状态,并且释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁,等待其他线程调用“锁标志“对象的notify或notifyAll方法恢复
  • wait方法是一个本地方法,其底层是通过一个叫做监视器锁的对象来完成的,所以调用wait方式时必须获取到monitor对象的所有权即通过Synchronized关键字,否则抛出IllegalMonitorStateException异常

notify/notifyAll()方法

  • 在同一对象上去调用notify/notifyAll方法,就可以唤醒对应对象monitor上等待的线程了。notify和notifyAll的区别在于前者只能唤醒monitor上的一个线程,对其他线程没有影响,而notifyAll则唤醒所有的线程

(3)sleep/yield/join方法解析

sleep

  • sleep方法的作用是让当前线程暂停指定的时间(毫秒)
  • wait方法依赖于同步,而sleep方法可以直接调用
  • sleep方法只是暂时让出CPU的执行权,并不释放锁。而wait方法则需要释放锁

yield

  • yield方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止
  • yield只能使同优先级或更高优先级的线程有执行的机会

join

  • 等待调用join方法的线程结束,再继续执行。如:t.join(),主要用于等待t线程运行结束
  • 作用是父线程等待子线程执行完成后再执行,换句话说就是将异步执行的线程合并为同步的线程

(4)不推荐使用方法解释

参考:Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated?

suspend()和resume()

  • 这两个方法是配套使用的,suspend()是暂停线程,但并不释放资源,容易造成死锁情况

stop()

  • 因为调用stop会使线程释放所有的锁,导致不安全情况,在调用stop时候,由锁保护的临界区可能处于状态不一致的情况,这不一致状态将暴露给其他线程
  • 推荐的做法是,维护一个状态变量,当线程需要停止时更改这一状态变量,该线程应检查这一状态变量,看该线程是否应该终止了

(5)关于interrupt()中断函数

  • 其实调用这个函数并不是真的中断线程,这个函数只是将Thread中的interrupt标志设置为true,用户需自行检测这一变量,停止线程,这种做法避免了stop带来的问题

推荐:http://blog.csdn.net/zolalad/article/details/38903911