• 多线程编程学习三(线程间通信).


    一、概要

        线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程间的通信就是成为整体的必用方案之一。可以说,使线程进行通信后,系统之间的交互性会更强大,在大大提高cpu利用率的同时还会使程序员对各线程任务在处理过程中进行有效的把控和监督。

    二、等待/通知机制

    1、"wait/notify"机制:等待/通知机制,wait使线程暂停运行,而notify 使暂停的线程继续运行。用一个厨师和服务员的交互来说明:

    (1) 服务员取到菜的时间取决于厨师,所以服务员就有“等待”(wait)的状态。

    (2) 厨师将菜放在“菜品传递台”上,其实就相当于一种通知(notify),这时服务员才可以拿到菜并交给就餐者。

    2、wait()

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

    (2) 在调用wait()方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法,否则抛出IllegalMonitorStateException异常。(属于Runtime的一个子类,不需要try-catch 语句进行捕捉异常)

    tips:为什么调用wait()方法一定要加锁呢?首先:wait()和notify() 都是属于Object的方法,并不是属于Thread的方法,只会辨识到Object类,并不会辨识到Thread类 。其二:wait()方法必须有noyify()方法匹配才能实现线程间通信。那么!同属于Object类的notify()方法怎么知道要唤醒哪个wait()呢?就是通过锁机制来判断的啦!否则,一个notifyAll()岂不是把所有等待的wait()线程,即使是不同锁的唤醒了?

    (3) 在调用wait()方法之后,当前线程释放锁,而此对象会进入线程等待池中,等待被唤醒。在从wait()返回前,线程与其他呈wait线程竞争重新获得锁。

    (4) wait()方法可以被interrupt 打断并抛出InterruptedException。

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

    3、notify()

    (1) 用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。(注意!这里说的是等待,即在执行完notify()方法后,当前线程不会马上释放该对象锁,即wait()状态的线程也不会马上获得对象锁,需要将synchronized 代码块中的代码执行完后才释放锁!)

    (2)也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁,否则也会抛出IllegalMonitorStateException.

    (3)当notify()发出通知,却没有wait()线程在等待时,则不作作用。

    4、notifyAll()

    (1) 可以使所有正在等待队列中的 等待同一共享资源(即同一个锁) 的"全部"线程从等待状态退出,进入可运行状态。

    5、

    6、假死:“假死”现象其实就是线程进入WAITING等待状态。如果全部线程都进入WAITING状态,则程序就不再执行任何功能了,整个项目呈停止状态。 出现这样的原因是因为:比如多个生产者和多个消费者的问题,“生产者”可能唤醒“生产者”,“消费者”可能唤醒“消费者”,唤醒了同类,导致线程不断在等待。怎么解决这个问题呢?将notify() 改成 notifyAll()方法即可,也就是将异类一同唤醒就可以了。

    7、 Jave中 管道流(pipeStream)是一种特殊的流,可用于在不同的线程中直接传送数据。一个线程发送数据到输出管道,另一个线程从输入管道中读数据。通过使用管道,实现不同线程间的通信,而无须借助于类似临时文件之类的东西。JDK中提供了四个类来使线程间可以通信,其中包括字节流(PipedOutputStream、PipedInputStream)和字符流(PipedWriter、PipedReader)。

    public class Piped {
    
        public static void main(String[] args) throws Exception {
            // 定义成字节流也可以
            PipedWriter out = new PipedWriter();
            PipedReader in = new PipedReader();
            // 将输出流和输入流进行连接,否则在使用时会抛出IOException
            out.connect(in);
            Thread printThread = new Thread(new Print(in), "PrintThread");
            printThread.start();
            int receive;
            try {
                while ((receive = System.in.read()) != -1) {
                    out.write(receive);
                }
            } finally {
                out.close();
            }
        }
    
        static class Print implements Runnable {
            private PipedReader in;
    
            public Print(PipedReader in) {
                this.in = in;
            }
    
            @Override
            public void run() {
                int receive;
                try {
                    while ((receive = in.read()) != -1) {
                        System.out.print((char) receive);
                    }
                } catch (IOException ex) {
                }
            }
        }
    }

    三、方法join的使用

    1、在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时计算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到 join() 方法了。

    2、join() 的作用是等待线程销毁,而使当前线程进行无限期的阻塞,等待 join() 的线程销毁后再继续执行当前线程的代码。

    3、同样的,join() 方法可以被 interrupt() 方法打断并抛出 InterruptedException 异常。

    4、join() 方法是 wait/notify 范式的简洁应用。当子线程调用 join() 时,主线程执行 wait() 方法进入等待,释放锁。子线程得到锁,执行结束后调用 notifyAll() 方法,释放锁。主线程接着执行。

    5、join 与 synchronized 的区别?

    (1) join()在内部使用wait() 方法进行等待。

    (2) synchronized 关键字使用的是“对象监视器”原理作为同步。

    6、方法 join(long) 和 sleep(long)的区别?

    (1) join(long)内部采用wait(long)方法实现,当执行wait(long)方法后,当前线程的锁被释放,那么其他线程也可以调用此线程中的同步方法了。即 join(long)之后,该线程释放锁,又需要和其他线程去争抢锁的资源。

    (2) Thread.sleep(long)方法不释放锁。

    四、类 ThreadLocal 的使用

    1、变量值的共享可以使用public static 变量的形式,所有的线程都使用同一个public static 变量。如果想实现每一个线程都有自己的共享变量该如何解决呢?类ThreadLocal解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全局存放数据的盒子,盒子中可以存放每个线程的私有数据。

    2、类ThreadLocal 具有隔离性,即每个线程都可以存入自己线程的数据而互不影响,而取到的也是自己线程存入的数据。

     

    五、类InheritableThreadLocal的使用

    1、InheritableThreadLocal类继承于ThreadLocal类,所以它具有ThreadLocal类的特性,但又是一种特殊的ThreadLocal,其特殊性在于InheritableThreadLocal变量值会自动传递给所有子线程,而普通ThreadLocal变量不行;而且,通过重写这个类中的 childValue 方法,子线程的值可以作为父线程值的一个任意函数。

    备注:

    (1)  什么是子线程? 

    包含在 Thread thread = new Thread(new ThreadStart(delegate{ 
    }));里面均视为子线程。(个人理解)

    (2) 什么是主线程? 

    UI界面和Main函数均为主线程,除了“不包含在Thread里面的程序”均可视为主线程。(个人理解)

  • 相关阅读:
    网格模型和X文件使用面面观(转)
    3D中的OBJ文件格式详解(转载)
    机器学习随笔01
    WinForm ListControl MouseWheel Envent
    如何判断一个元素在亿级数据中是否存在? 很难吗...
    windows 虚拟机VMware 安装linux系统注意事项!!!
    windows phpinfo上不能找到memcache扩展 php版本5.6
    php 判断两个时间段是否有交集
    tp5.0 根据经纬度 获取附近信息
    php 前台生成多维数组 后台批量添加
  • 原文地址:https://www.cnblogs.com/jmcui/p/7503296.html
Copyright © 2020-2023  润新知