黑马程序员技术交流社区

标题: 石家庄校区就业班 day07线程间通信 [打印本页]

作者: 18032086639    时间: 2018-11-26 14:08
标题: 石家庄校区就业班 day07线程间通信
本帖最后由 18032086639 于 2018-11-26 14:11 编辑

day07线程间通信

线程间通信介绍
线程间通信: 多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同(需要协作)
为什么要进行线程间通信:
        通常是竞争关系: 多个线程并发执行时, 在默认情况下CPU是随机切换线程的.
        有时也需要合作: 当我们需要多个线程来共同完成一件任务, 并且我们希望他们有规律的执行, 多线程之间需要一些协调通信, 以此来帮我们达到多线程共同操作一份数据
如何保证线程间通信有效利用资源:
        等待唤醒机制
等待唤醒机制介绍
等待唤醒机制:
        一个线程进行了规定操作后, 就进入等待状态( wait() ), 等待其他线程执行完他们的指定代码过后, 再将其唤醒( notify() )
    在有多个线程进行等待时, 如果需要, 可以使用 notifyAll() 来唤醒所有的等待线程
        *`<!--wait/notify 就是线程间的一种协作机制, 用于解决线程间通信的问题-->
等待唤醒中的方法
java.lang.Object类:
        // 成员方法 (只能通过"锁对象"调用)
        void notify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程
        void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
        void wait(): 让当前线程处于无限等待状态, 同时释放锁
wait和notify/notifyAll的执行原理:
        wait:
                线程不再活动, 不再参与调度, 进入 wait set 中, 因此不会浪费 CPU 资源, 也不会去竞争锁, 这时的线程状态即是"WAITING". 它还要等着别的线程执行"通知(notify)", 让在锁对象上等待的线程从 wait set 中释放出来, 重新进入到调度队列(ready queue)中
        notify/notifyAll:
                哪怕只通知了一个等待的线程, 被通知线程也不能立即恢复执行, 因为它当初中断的地方是在同步块内, 而
此刻它已经不持有锁, 所以它需要再次尝试去获取锁(很可能面临其它线程的竞争), 成功后才能在当初调用 wait() 之后的地方恢复执行
                总结如下:
                       如果能获取锁, 线程就从"WAITING"状态变成"RUNNABLE"状态
                        否则, 从 wait set 出来, 又进入 entry set, 线程就从"WAITING"状态又变成"BLOCKED"状态
调用 wait() 和 notify() 需要注意的细节:
        1. wait() 与 notify() 必须要由"同一个锁对象"调用
              应的锁对象可以通过 notify() 唤醒使用同一个锁对象调用的 wait() 后的线程
        2. wait() 与 notify() 是属于Object类的方法
              对象可以是任意对象, 而任意对象的所属类都是继承了Object类的
        3. wait() 与 notify() 必须要在"同步代码块"或者是"同步方法"中使用
              须要通过锁对象调用这2个方法
             线程池线程池概念和原理
       普通创建线程方式的缺点:
                         "创建"线程和"销毁"线程都是比较占用内存和CPU的操作.
                      对于一些数 量多, 执行时间短的任务, 频繁的创建和销毁线程来执行, 会降低程序运行效率.
              线程池:
                      一个容纳多个线程的容器
              线程池可以解决的问题:
                      其中的线程可以反复使用, 省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源
线程池的工作原理:
                         提前创建好多个线程对象, 放在集合中. 多个任务来了反复使用这些线程对象来执行
线程池的代码实现
                                      一哥载Q特儿斯
              java.util.concurrent.Executors类: 线程池工厂类, 用于管理线程池
                      // 静态方法:
                      static ExecutorService newFixedThreadPool(int nThreads): 创建固定数量线程的线程池(常用)
                                   一哥载Q特儿 色儿微斯
              java.util.concurrent.ExecutorService接口: 真正执行任务的线程池服务
                      // 成员方法:
                      Future submit(Runnable task): 提交一个Runnable任务
                      void shutdown(): 通知线程执行完任务后关闭. 如不调此方法, 则线程执行 完任务后仍在运行以便重复使用
线程池的创建和使用步骤:
                 1. 使用Executors的静态方法 newFixedThreadPool(int nThreads) 创建线程池ExecutorService
                2. 创建一个任务类, 实现Runnable接口, 重写run()方法
                        3. 调用ExecutorService对象的 submit(Runnable task) 方法, 传递任务给线程池, 执行任务
                         4. 调用ExecutorService对象的 shutdown() 方法, 销毁线程池 (不建议执行)
函数式编程思想
Lambda达式函数式编程思想概述
函数式:
        在数学中, 函数就是有输入量, 输出量的一套计算方案, 也就是"拿什么东西做什么事情"
面向对象: 强调"用哪个对象的哪个方法"来做事 (注重语法形式)
函数式: 强调做事 (不关心用什么对象, 重写什么方法)
函数式编程的好处: 简化代码编写
冗余的Runnable代码
    // 我们要通过Lambda表达式简化以下的代码
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 要执行的代码才是重要的
        }
    }).start();
关键代码是: run()方法中要执行的任务
而其他代码都只是形式
编程思想的转换, 体验Lambda更优写法
JDK 8 中加入的Lambda表达式, 是函数式编程思想中的重点
对比:
    // 面向对象方式的代码
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "新线程创建了");
            }
        }).start();
        // 函数式编程的代码
        new Thread(()-> {
                System.out.println(Thread.currentThread().getName() + "新线程创建了");
            }
        ).start();
    格式:
            (参数列表)->{一些重要的方法代码};
    解析说明格式:
    ():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔
    ->:        传递的意思,把参数传递给方法体{}
    {}:重写接口的抽象方法的方法体
Lambda标准格式
Lambda表达式的3个部分:
    1. 一些参数 ()
                接口中抽象方法的参数列表. 没参数就空着; 有参数就写, 多个参数用逗号分隔
    2. 一个箭头 ->
                将参数传递给方法体
    3. 一段代码 {}
                重写接口抽象方法的方法体
格式:
        // 写成一行
        (参数列表) -> {一些重写方法的代码}
        // 写成多行
        (参数列表) -> {
        一些重写方法的代码
    }
Lambda省略格式和使用前提
省略原则:
        可推导的都可省略 (凡是能根据前后代码能猜测出来的代码, 都可以省略不写)
可以省略的部分:
        1. (参数列表): 参数"类型"可以省略
        2. (参数列表): 如果参数只有1个, 则"类型"和"小括号"都可以省略  a -> sout(a)
        3. {一些代码}: 如果只有一条代码, 则"大括号", "return", "分号"都可以"一起省略"
*函数式接口:
        函数式接口"有且仅有一个抽象方法"
        但函数式接口对于哪些方法算作抽象方法有特殊规定:
                1. 有方法体的方法"不算作"抽象方法, 如默认方法, 静态方法, 私有方法
                2. 抽象方法与java.lang.Object类中的方法定义相同的, 也"不算作"抽象方法
                        因为任何实现本接口的实现类, 都会直接或间接继承java.lang.Object类的public的方法, 所以在创建实现类时其实不用重写该抽象方法, 也就不算作抽象方法

Lambda表达式的使用前提:
    1. Lambda只能用于接口, 且"接口中有且仅有一个抽象方法"(也称为"函数式接口")
        普通类, 抽象类不能用
    2. 使用Lambda必须具有上下文推断
        方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例
        (简而言之: 作为参数类型的接口, 必须是函数式接口)
        比如以下构造方法
        比如以下构造方法
        Thread(Runnable r): 该构造方法的参数类型是Runnable





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2