黑马程序员技术交流社区

标题: day07 线程间通信 线程池 Lambda表达式 [打印本页]

作者: i欸你看    时间: 2018-11-26 17:20
标题: day07 线程间通信 线程池 Lambda表达式
day07 线程间通信 线程池 Lambda表达式
线程间通信 线程间通信介绍
等待唤醒机制介绍
知识点:
多线程    线程间通信 (等待唤醒案例)    线程池 函数式编程    Lambda表达式        标准格式        省略格式
为什么要进行线程间通信 进行线程间通信要使用什么技术
线程间通信:     多个线程在处理同一个资源, 但是多个线程的处理动作却不相同(线程的任务不同, 需要协调合作)
为什么要进行线程间通信:    通常是竞争关系: 多个线程并发执行时, 在默认情况下CPU是随机切换线程的.    有时也"需要合作": 当我们需要多个线程来共同完成一件任务, 并且我们希望他们有规律的执行, 那么多线程之 间需要一些协调通信, 以此来帮我们达到多线程共同操作一份数据
如何实现线程间通信:    "等待唤醒机制"
什么是等待唤醒机制 了解 wait/notify 的执行流程
等待唤醒机制:    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的操作.     
对于一些数量多, 执行时间短的任务, 频繁的创建和销毁线程来执行, 会降低程序运行效率.   
线程池:    一个容纳多个线程的容器(集合)    线程池可以解决的问题:    其中的线程可以反复使用, 省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源   
线程池的工作原理:    提前创建好多个线程对象, 放在集合中. 多个任务来了反复使用这些线程对象来执行
函数式编程思想: Lambda表达式
函数式编程思想概述
函数式:     在数学中, 函数就是有输入量, 输出量的一套计算方案, 也就是"传入什么东西, 得到什么结果"    y = f(x)
面向对象: 强调"用哪个对象的哪个方法"来做事 (注重语法形式: 继承 方法重写) 函数式: 强调"传入的参数 和 要执行的代码"
函数式编程的好处:     简化代码编写 (使用 λ Lambda表达式, 简化匿名内部类的代码)
Lambda标准格式
Lambda表达式的标准格式是什么样的, 由哪3部分组成
总结:
Lambda表达式是哪个JDK版本中加入的 体会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表达式的3个部分:    1. 一些参数 ()        接口中抽象方法的参数列表. 没参数就空着; 有参数就写, 多个参数用逗号分隔   
2. 一个箭头 ->        将参数传递给方法体   
3. 一段代码 {}        重写接口抽象方法的方法体
格式:    // 写成多行    (数据类型 变量名, 数据类型 变量名) -> {一些重写方法的代码}
函数式接口:   
函数式接口: "有且仅有一个抽象方法的接口"    但函数式接口对于 哪些方法算作抽象方法 有特殊规定:        
1. 有方法体的方法"不算作"抽象方法, 如默认方法, 静态方法, 私有方法      
2. 如果一个抽象方法 与 java.lang.Object类中的方法 定义相同的, 也"不算作"抽象方法            因为任何实现本接口的实现类, 都会直接或间接继承java.lang.Object类的public的方法, 所以 在创建实现类时其实不用重写该抽象方法, 也就不算作抽象方法
Lambda省略格式和使用前提
省略原则:    "可推导的都可省略" (凡是能根据前后代码能猜测出来的代码, 都可以省略不写)           
可以省略的部分:      
1. (参数列表): 参数"类型"可以省略 (a, b) -> {}      
2. (参数列表): 如果参数只有1个, 则"类型"和"小括号"都可以省略  a -> sout(a)      
3. {一些代码}: 如果只有一条代码, 则"大括号", "return", "分号"都可以"一起省略"                    
*函数式接口:    函数式接口: "有且仅有一个抽象方法的接口"    但函数式接口对于 哪些方法算作抽象方法 有特殊规定:      
1. 有方法体的方法"不算作"抽象方法, 如默认方法, 静态方法, 私有方法        
2. 如果一个抽象方法 与 java.lang.Object类中的方法 定义相同的, 也"不算作"抽象方法            因为任何实现本接口的实现类, 都会直接或间接继承java.lang.Object类的public的方法, 所以 在创建实现类时其实不用重写该抽象方法, 也就不算作抽象方法

Lambda表达式的使用前提:   
1. Lambda只能用于接口, 且"接口中有且仅有一个抽象方法"(也称为"函数式接口")        普通类, 抽象类不能用   
2. 使用Lambda必须具有上下文推断        接口中只能有一个抽象方法, 才能推断出来重写的是这个抽象方法
今日API
java.lang.Object类:    // 成员方法    void wait(): 使用锁对象调用, 当前线程进入WAITING无限等待状态, 直到被其他线程唤醒   
void notify(): 使用锁对象调用, 随机唤醒一个处于等待状态的线程   
void notifyAll(): 使用锁对象调用, 唤醒所有处于等待状态的线程
java.util.concurrent.Executors类: 线程池工厂类, 用于管理线程池   
// 静态方法:    static ExecutorService newFixedThreadPool(int nThreads): 创建固定数量线程的线程池(常用)    java.util.concurrent.ExecutorService接口: 真正执行任务的线程池服务   
// 成员方法:    Future submit(Runnable task): 提交一个Runnable任务    void shutdown(): 通知线程执行完任务后关闭. 如不调此方法, 则线程执行完任务后仍在运行以便重复使用
day-08 File类 递归
File类的静态成员变量
java.io.File类: 文件和目录的路径名的抽象表现形式, 主要用于文件和目录的创建, 查找和删除等操作
        // 静态成员变量
    static String pathSeparator: 路径分隔符的字符串形式
    static char pathSeparatorChar: 路径分隔符的char形式
         Windows系统是 分号;  
        Linux系统是 冒号:
    static String separator: 文件名称分隔符的字符串形式
    static char separatorChar: 文件名称分隔符的char形式
       Window系统是 反斜杠\
       Linux系统是 正斜杠/
绝对路径和相对路径
绝对路径:
        以盘符开始的路径
        如: "D:\\a\\hi.txt"
相对路径:
        不以盘符开始的简化路径. 相对于项目的根目录
        如: "a\\1.mp3", "123.txt"
注意事项:
        1. 路径不区分大小写 (在Windows系统中不区分大小写, Linux, Mac区分)
        2. 路径一般写成字符串, 而字符串中一个\是转义, 所以要写两个\\
File类: 构造方法
java.io.File类: 文件和目录的路径名的抽象表现形式, 主要用于文件和目录的创建, 查找和删除等操作
        // 构造方法(创建了File对象, 并将其指向该路径. 不会真正在磁盘上创建这个文件)
        File File(String pathname): 根据 路径字符串 封装一个File对象
        File File(String parent, String child): 根据 父路径字符串 和 子路径字符串 封装File对象
        File File(File parent, String child): 根据 父路径的File对象 和 子路径 封装File对象
java.io.File类
    // 常用获取方法
    String getAbsolutePath(): 返回此File的绝对路径名字符串
    String getPath(): 获取File对象的封装路径 (创建对象时传入的路径)
    String getName(): 获取File对象的文件名或目录名
    long length(): 获取File表示的文件大小的字节数 (不能获取目录的大小)
File类: 判断方法
java.io.File类
    // 常用判断方法
        boolean exists(): 判断File对象代表的文件或目录是否实际存在
        boolean isDirectory(): 判断File表示的是否为目录
        boolean isFile(): 判断File表示的是否为文件
File类: 创建删除方法
java.io.File类
    // 常用创建删除方法
        boolean createNewFile(): 当文件不存在时, 创建一个新的空文件
        boolean delete(): 删除由此File表示的文件或目录. (删除目录时必须是空目录)
        boolean mkdir(): 创建File表示的目录
        boolean mkdirs(): 创建File表示的多级目录
File类: 遍历目录方法
java.io.File类
    // 常用获取目录中内容的方法
        String[] list(): 获取当前File目录下的所有子文件或目录的名字数组
        File[] listFiles(): 获取当前File目录中的所有子文件或目录的File对象数组   
注意:
        只能用表示目录的File对象调用
        用文件的File对象, 或者路径不存在, 调用会报错
递归递归的概念, 分类, 注意事项
递归思想:
        遇到一个问题时, 将该问题拆解成可以解决的小问题, 如果解决不了, 继续拆解为更小的问题. 如果小问题解决了, 大问题也就能够解决
Java中实现递归的方式:
        方法内部调用方法自己 (所以必须定义方法)
递归的分类:
        直接递归: 方法自己调用方法
        间接递归: A方法调用B方法, B方法调用C方法, C方法调用A方法
递归时的注意事项:
    1. 递归要有限定条件(出口), 保证递归能够停止(就是在某种情况下方法不再调用自己), 否则会栈内存溢出
    2. 递归次数不能太多, 否则会栈内存溢出
    3. 构造方法不能递归
递归的使用前提:
        调用方法时, 方法的主体不变, 但每次传递的参数值不同, 可以使用递归
FileFilter文件过滤器的原理和使用
java.io.File类:  Filter过滤器
        File[] listFiles(FileFilter filter): 通过File对象过滤, 返回文件过滤器过滤后的File对象数组
        File[] listFiles(FilenameFilter filter): 通过File对象的文件名过滤, 返回文件过滤器过滤后的File对象数组
java.io.FileFilter接口: 用于File对象的过滤器
        boolean accept(File pathName): true则会将参数的File对象加入返回的File[], false则不加入
java.io.FilenameFilter接口:
        boolean accept(File dir, String name): true则会将参数的File对象加入返回的File[], false则不加入
day09 字节流 字符流 Properties
字节流 计算机中一切皆为字节
补充:
  字节输出流: OutputStream和FileOutputStream
知识点:
计算机中存储的数据是什么形式的
计算机中, 无论文本, 视频, 音频, 图片... 一切都是以"字节byte"形式存储的 也就是以数字形式存储的, 而数字可以用不同进制表示, 计算机能看懂的是二进制数字
字节输出流: OutputStream和FileOutputStream
字节输出流: 一次写一个字节到文件
写数据的原理:    Java程序 -> JVM虚拟机 -> OS操作系统 -> OS调用写数据的方法 -> 将数据写入文件    使用字节输出流写数据到文件的步骤:    1. 创建对象: 创建FileOutputStream对象, 构造方法中传递文件路径    2. 写入数据: 使用FileOutputStream对象调用 write(int b) 方法, 将字节写入文件    3. 释放资源: 使用FileOutputStream对象调用 close() 方法, 关闭流对象释放资源
文件存储原理和记事本打开文件的原理
向文件中写入字节数据时, 十进制的数字会被转换为"二进制"的数字写入文件
文本编辑器打开文本文件时, 会先查询编码表, 将二进制数字转换为对应的字符进行显示    0-127: 查询ASCII码表  -128~127    其他: 查询系统默认码表             Windows简体中文系统的程序打开是按 GBK 码表            IDEA中使用的是 UTF-8 码表   
ASCII编码表: 1个byte就是一个字符  97 a GBK编码表:   2个byte数字组成一个汉字. "你": -60, -29 UTF-8编码表: 3个byte数字组成一个汉字. "你": -28, -67, -96
字节输出流: 一次写多个字节
java.io.FileOutputStream类: 文件字节输出流 (向文件写数据)    void write(byte[] b): 一次写一个字节数组    void write(byte[] b, int off, int len): 一次写一个字节数组的一部分           
Java中, byte的范围是 -128 ~ 127 共256个数值 编码表中字符范围是       0 ~ 255 共256个数值    写多个字节时:    如果第一个字节是正数中的 0~127, 则记事本会查询 ASCII码表 显示字符    如果第一个字节是负数中的:-128~-1, 则记事本会查询 GBK码表 显示字符.(将两个连续的byte组合为一个中 文)
字节输出流: 续写, 换行
java.io.FileOutputStream类: 文件字节输出流    // 带有 续写 功能的构造方法, 不会清空文件    FileOutputStream(String name, boolean append): 通过文件路径创建流, true可以续写    FileOutputStream(File file, boolean append): 通过File对象创建流, true可以续写    换行符:    Windows系统: "\r\n"    Linux系统: "\n"    MacOS系统: "\r"
字节输入流: InputStream和FileInputStream
java.io.InputStream抽象类: 字节输入流 (顶层类)    // 常用成员方法    void close(): 释放资源   
// 读数据的方法    int read(): 一次读一个字节        读到文件末尾返回-1 (返回int也是为了代码编写方便)    int read(byte[] b): 一次读一个字节数组         读到的字节存放在参数中的字节数组中, 返回int值是本次读到的字节的个数. 读到文件末尾返回-1     java.io.FileInputStream类: 文件字节输入流   
// 构造方法    FileInputStream(String name): 使用文件路径创建文件字节输入流    FileInputStream(File file): 使用File对象创建文件字节输入流    构造方法的作用:        1. 创建FileInputStream对象        2. 将FileInputStream对象指向磁盘上的文件
字节流读取中文问题
知识点:
字节流读取中文是否方便? GBK编码中, 一个汉字占用几个byte UTF-8编码中, 一个汉字占用几个byte
字符流
字符输入流: Reader和FileReader
知识点:
Reader类有哪些共性方法 FileReader类可以做什么
// 读到多少写多少, 不要全写            fos.write(bytes, 0, len);        }
// 释放资源        fos.close();        fis.close();
long end = System.currentTimeMillis();        System.out.println("复制耗时:" + (end - start) + "毫秒");        // 一次读写一个字节     复制耗时:1309毫秒        // 一次读写一个字节数组 复制耗时:35毫秒    } }

GBK编码中, 一个汉字占用2个byte UTF-8编码中, 一个汉字占用3个byte
JDK7和JDK9中IO异常处理的不同方式
JDK 7 增加的 "try-with-resource"    省略了 finally, 可自动释放资源        // 格式    try (创建流对象语句,如果多个,使用';'隔开) {        // 读写数据        } catch (IOException e) {        e.printStackTrace();        }        // 示例    try (FileWriter fw = new FileWriter("fw.txt");FileReader fr = new FileReader("fr.txt")) {        // IO操作        int ch = fr.read();        fw.write(ch);    } catch (IOException e) {        e.printStackTrace();    }


JDK 9 对于"try-with-resource"的改进:    流对象的声明和创建可以放在括号外面    流对象要求是有效final的.(即不要修改流对象的值)

    FileWriter fw = new FileWriter("fw.txt");    FileReader fr = new FileReader("fr.txt");    try (fw; fr) {        // IO操作        int ch = fr.read();        fw.write(ch);
补充:
  Properties集合
Properties存储数据和遍历
知识点:
Properties双列集合存储的键和值是什么数据类型 Properties双列集合有哪些特有方法
总结:
补充:
    } catch (IOException e) {        e.printStackTrace();    }
day10 缓冲流 转换流 序列化流 打印流

缓冲流
缓冲流的原理

缓冲流的原理:    底层也是使用基本流(FileXxx)来读写    但缓冲流内部定义了一个缓冲数组, 在读的时候类似于我们一次读一个数组的方式, 减少了磁盘操作次数, 提高 了程序效率
缓冲字节输出流: BufferedOutputStream
java.io.BufferedOutputStream类: 缓冲字节输出流   
// 构造方法    BufferedOutputStream(OutputStream out): 使用基本流创建一个缓冲字节输出流    BufferedOutputStream(OutputStream out, int size): 使用基本流创建一个缓冲字节输出流, 设置 缓冲区大小    BufferedOutputStream使用步骤:   
1.创建FileOutputStream对象, 构造方法中绑定要输出的目的地   
2.创建BufferedOutputStream对象, 构造方法中传递FileOutputStream对象   
3.使用BufferedOutputStream对象中的方法 write(), 把数据写入到内部缓冲区中   
4.使用BufferedOutputStream对象中的方法 flush(), 把内部缓冲区中的数据,刷新到文件中   
5.释放资源(会先调用flush方法刷新数据, 第4步可以省略)            
FileOutputStream fos = new FileOutputStream("文件路径");    BufferedOutputStream bos = new BufferedOutputStream(fos);    bos.write("你好".getBytes());    // bos.flush();  // 可以省略    bos.close();
缓冲字节输入流: BufferedInputStream
java.io.BufferedInputStream类: 缓冲字节输入流   
// 构造方法    BufferedInputStream(InputStream in): 使用基本流创建一个缓冲字节输入流    BufferedInputStream(InputStream in, int size): 使用基本流创建一个缓冲字节输入流, 设置缓冲 区大小    使用步骤:   
1.创建FileInputStream对象, 构造方法中绑定要读取的数据源   
2.创建BufferedInputStream对象, 构造方法中传递FileInputStream对象   
3.使用BufferedInputStream对象中的方法 read(), 读取文件   
4.释放资源 close()           
FileInputStream fis = new FileInputStream("文件路径");    BufferedInputStream bis = new BufferedInputStream(fis);    byte[] bytes = new byte[1024];    int len;    while ((len = bis.read(bytes)) != -1) {        System.out.println(new String(bytes, 0, len));    }    bis.close();
缓冲字符输出流: BufferedWriter
java.io.BufferedWriter类:   

// 构造方法    BufferedWriter(Writer out): 使用基本流创建一个缓冲字符输出流    BufferedWriter(Writer out, int size): 使用基本流创建一个缓冲字符输出流, 设置缓冲区大小   
// 特有方法    void newLine(): 写入一个换行符, 换行符自动根据当前系统确定
缓冲字符输入流: BufferedReader
java.io.BufferedReader类: 缓冲字符输入流   

// 构造方法    BufferedReader(Reader in): 使用基本流创建一个缓冲字符输入流    BufferedReader(Reader in, int size): 使用基本流创建一个缓冲字符输入流, 设置缓冲区大小   
// 特有方法    String readLine(): 一次读一行字符串, "不包含换行符". 读到文件末尾返回null
转换流
字符编码和字符集
编码: 字符 -> 字节  'a' -> 97 解码: 字节 -> 字符  97 -> 'a'
编码表: 字符和二进制数字的对应规则     字符集和编码表: 字符集包含编码表    ASCII字符集        ASCII编码表        ASCII扩展编码表    ISO-8859-1字符集: Tomcat Web服务器程序        Latin-1: 拉丁字符. 没有中文. 每个字符由1个byte组成    GB字符集
补充:
  乱码问题: FileReader读取GBK编码
  转换流原理: 字符流和转换流的关系
        GB2312编码表: 每个字符由2个byte组成        GBK编码表: 每个字符由2个byte组成        GB18030编码表: 每个字符由1, 2, 4个byte组成    Unicode字符集        UTF-8: ASCII字符占1个byte, 拉丁字符占2个byte, 中文占3个byte, Unicode辅助字符占4个byte        UTF-16        UTF-32    ANSI: 表示使用系统默认编码表
OutputStreamWriter类介绍及使用


java.io.OutputStreamWriter类: 输出转换流. 字符流通往字节流的桥梁    // 构造方法    OutputStreamWriter(OutputStream out): 使用默认编码表创建转换流    OutputStreamWriter(OutputStream out, String charsetName): 使用指定编码表创建转换流
// 使用默认UTF-8 OutputStreamWriter o = new OutputStreamWriter(new FileOutputStream("a.txt")); o.write("dsfdsfdsaf")
// 使用指定的GBK OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"), "GBK");        osw.write("你") -> 查"GBK"码表 -> -1,-2 -> FileOutputStream -> a.txt文件         写数据: 字符流 --------------------------> 字节流
InputStreamReader类介绍及使用


java.io.InputStreamReader类: 输入转换流. 字节流通往字符流的桥梁    // 构造方法    InputStreamReader(InputStream in): 使用默认编码表创建转换流    InputStreamReader(InputStream in, String charsetName): 使用指定编码表创建转换流    // 使用默认UTF-8 InputStreamReader r = new InputStreamReader(new FileInputStream("a.txt"));
// 使用指定的GBK InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"), "GBK");     '你' <- 查"GBK"码表 <- isr.read() <- -1,-2 <- FileInputStream <- a.txt文件                         读数据: 字符流 <---------- 字节流
序列化流(对象流)

序列化和反序列化概述
序列化: 内存中的对象转换为字节序列, 以流的方式写入到磁盘的文件中    对象 -> 字节 反序列化: 磁盘文件中的字节序列, 以流的方式读取到内存中变成对象    字节 -> 对象
通过序列化流, 我们可以将内存中的数据方便的存储到磁盘上, 下次程序启动后也能从磁盘读取恢复之前的对象状态
OutputStream    |_ ObjectOutputStream类: 对象字节输出流 InputStream    |_ ObjectInputStream类: 对象字节输入流
对象序列化流: ObjectOutputStream


java.io.ObjectOutputStream类: 对象字节输出流   
// 构造方法    ObjectOutputStream(OutputStream out)   
// 特有成员方法    void writeObject(Object obj): 将对象写出   
// 创建对象输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.txt"));
// 写对象 Student s = new Student("小美女", 18); oos.writeObject(s); // 释放资源 oos.close();

注意:    被读写的对象的类必须实现"java.io.Serializable"接口, 否则会抛出"NotSerializableException"
对象反序列化流: ObjectInputStream
java.io.ObjectInputStream类: 对象字节输入流    // 构造方法    ObjectInputStream(InputStream in)   
// 特有成员方法    Object readObject(): 读取对象   
// 创建对象输入流 ObjectInputStream oos = new ObjectInputStream(new FileInputStream("student.txt"));
// 读对象 Object o = oos.readObject(); Student s = (Student)o; System.out.println(s);
transient瞬态关键字: 避免属性序列化
  InvalidClassException异常: 原因和解决方案
// 释放资源 oos.close();
static 修饰的成员变量值, 能否序列化 transient 修饰的成员变量值, 能否序列化
static 修饰的成员变量属于类不属于对象, 所以不能序列化 transient 修饰的成员变量, 不能被序列化
transient 应用场景:    如果对象的某个属性不希望被序列化, 可以使用 transient 修饰, 这样就不会被对象流写到文件中







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