【视频笔记】Java并发(狂神)

文章目录

  • 多线程基础
    • 1. 进程和线程
      • 概念
    • 2. 线程创建(三种)
      • a.Thread 类-继承
        • 文档
        • 使用总结
        • 代码测试
        • 案例:下载图片
      • b.Runnable 接口-实现()
        • 文档
        • 区别:
        • 一个对象被多个线程操作的情况
        • 案例:龟兔赛跑
      • c.实现Callable接口(了解即可)
        • 区别
        • 代码
        • Callable好处
        • 并发问题
    • 3. 剖析Thread类
      • a.静态代理(StaticProxy)
        • 定义
        • 这个接口的使用
        • 好处
      • b.Lamda表达式
        • 目的
        • 函数式接口
        • 简化方法
        • 推导
        • 书写结构
      • c.回归多线程
        • 使用
    • 4. 线程五大状态
      • 线程方法
      • 线程停止
      • 进程休眠
      • 进程礼让
        • 概念
      • 进程强制执行
        • 概念
        • 代码
      • 观测线程状态
        • 文档
        • 用代码观察
      • 线程优先级
        • 概念
        • 文档
      • 守护线程
        • 概念
        • 测试守护线程代码
    • 5. 线程并发
      • 线程同步
        • 概念
      • 代码:三大不安全的案例
        • 买票
        • 不安全的取钱
        • 线程不安全的集合ArrayList
        • 总结
      • 如何保证线程安全
        • synchronized关键字使用
          • 同步方法
        • 同步块
      • CopyOnWriteArrayList
        • JUC
        • 测试JUC安全
      • 死锁
        • 概念
        • 代码
        • 解决
      • Lock锁
        • 概念
        • 代码
        • 总结
    • 6. 线程通信
      • 生产者消费者问题
        • 问题背景
        • 解决思路
        • 解决方法
      • 管程法代码-利用缓冲区解决
      • 信号灯法-标志位
      • 线程池
        • 概念
        • 接口
        • 代码
  • 多线程进阶 JUC
    • 准备工作
      • 环境
    • 1.基础回顾
      • 进程状态wait和sleep的区别
      • Lock锁
      • Lock锁和Synchronized区别
      • 生产者和消费者问题
        • 虚假唤醒
        • Lock版的生产者消费者问题
        • Condition优势
    • 2. 锁是什么,如何判断锁的是谁。8个锁的问题
      • 锁的对象
      • 暂停
      • Synchronized锁时
        • 1.标准情况下,两个线程都加了Synchronized关键字
        • 2.其中一个线程,延迟4秒执行
        • 3.增加了一个普通线程。另外两个线程用了一把锁
        • 4.new了两个对象时
        • 5.两个对象,两个线程加了static静态方法
        • 6.一个对象,调用一个static锁线程和一个没加static锁的线程
        • 7.两个对象,每个都调用一个static锁线程和一个没加static锁的线程
        • 小结
    • 3. 集合类不安全
      • List不安全
        • CopyOnWriteArrayList
        • CopyOnWriteArrayList比Vector好在哪
      • Set不安全
        • HashSet底层是什么
      • Map不安全
        • ConcurrentHashMap
    • 3. JUC下剩下的几个类
      • Callable
        • 为什么需要
        • 使用介绍
      • CountDownLatch
        • 文档
        • 代码
      • CyclicBarrier
        • 代码
      • Semaphore
        • 代码
    • 2. 剩下的一个锁
      • 读写锁
        • 概念
        • 代码
    • 4. 阻塞队列
      • 阻塞队列 BlockingQueue
        • 概念
        • 文档
          • 何时使用
          • 其他队列
        • 四组API(四个方法)
        • 使用
      • 同步队列 SynchronousQueue
        • 代码
    • 5. 线程池
      • 概念
        • 池化技术
        • 线程池的好处
      • 代码来了解
      • 七大参数
        • 文档
        • 详细介绍
          • 举例
          • 概念
        • 代码,使用七大参数
      • CPU密集型和IO密集型
    • 6. Java新特性
      • 函数式接口
        • 函数型接口 Function
          • 文档
          • 总结
        • 断定型接口 Predicate
        • 消费型接口 consumer
        • 供给型接口 apply
      • Stream流计算
        • 概念
        • 文档
      • 链式编程
    • 7. 分支合并
      • ForkJoin
        • 概念
        • 特点
        • 求累加和
          • 思路
        • 实现
          • 文档
          • 代码
      • 使用并行流,把计算交给Stream流
      • 异步回调
        • Future
        • 异步调用
    • Volatile
      • 概念
      • 特性
      • 保证可见性,通过JMM
        • 什么是JMM
        • 哪些具体的约定
        • 主存和工作内存之间的八种操作
        • 存在的问题
        • 代码演示
        • 解决办法
      • 验证volatile的特性
        • 可见性
        • 不保证原子性
        • 指令重排
        • 如何避免指令重排的
    • 8. 单例模式
      • 饿汉式
        • 概念
        • 存在的问题
      • 懒汉式
        • 概念
        • 在单线程是没问题,但多线程不安全
        • 解决方法
        • 静态内部类
        • 反射
        • 解决这种破坏
        • 再尝试解决
        • 通过枚举类型防止单例被破坏
        • 枚举类型也有一点问题
    • 9. CAS
      • 概念
      • ++的底层
      • unsafe类
        • 概念
        • 方法和对象
      • 总结++
      • 总结CAS
      • ABA问题
        • 概念
        • 代码
        • SQL中
        • 解决办法:原子引用
          • 代码
    • 10. 各种锁的理解
      • 公平锁,非公平锁
        • 概念
      • 可重入锁
        • 概念
        • 代码
      • 自旋锁
        • 概念
        • 代码
      • 死锁排查
        • 概念
        • 代码
        • 四要素
        • 如何排查死锁
      • 其他锁

多线程基础

1. 进程和线程

概念

  1. 进程:在内存中运行了
  2. 程序:磁盘中
  3. 线程:真正执行的单位,独立执行
  4. 不需要创建线程时,也有默认线程存在
  5. 多线程需要开辟
    1. 需要管理
  6. 有可能数据不一致
  7. 使用时
    1. 线程就是一个资源类,线程就是一个类

2. 线程创建(三种)

a.Thread 类-继承

文档

  1. 继承于谁
  2. 实现了什么接口
  3. 文字介绍
    1. 优先权:执行的先后
    2. 守护线程
    3. 用户线程
  4. 创建方法
    1. 声明为Thread的子类
    2. 重写run方法
    3. 开启
      1. new 一个对象
      2. 对象.start();就可以开启

使用总结

  1. 自定义一个线程类
    1. 继承Thread类
  2. 重写run()方法,作为线程执行体
  3. 创建线程对象
  4. 调用start()方法

代码测试

  1. run方法
    1. 写个for循环中打印内容
  2. main方法
    1. 创建一个线程对象,调用start()
    2. 主线程中也打印,另一个内容
  3. start方法来启动
    1. 两个线程交替执行
  4. run方法
    1. 按顺序执行
  5. 线程开启不会立即执行

案例:下载图片

  1. 用多线程同时下载多个图片
  2. 用commons-io jar包
    1. 拷贝
    2. 然后添加
  3. 构造器?就是构造函数?
  4. 并不是按照代码顺序执行的

b.Runnable 接口-实现()

文档

  1. 类 实现Runnable接口
  2. 类中 重写run方法
  3. 启动
    1. runnable方法的实现类
    2. 创建线程对象new Thread,丢入实现类的对象作参数
    3. start()方法
  4. runnable的实现类
    1. new出来的只是一个参数
    2. 还要再放到thread对象中。
    3. thread才能使用
    4. 相当于一个代理
  5. 继承Thread
    1. new出来的thread对象就是可以使用的
  6. 接口可以new

区别:

  1. 相同点:
    1. 都是重写run方法
  2. 不同点
    1. Thread对象的创建需要参数
  3. 推荐使用runnable接口
    1. thread继承时,单继承具有局限性
    2. 实现接口的方法更灵活,一个对象可被多个线程用

一个对象被多个线程操作的情况

  1. 多个线程操作同一个资源,造成紊乱
    1. 进程同步问题

案例:龟兔赛跑

  1. 获取线程名字
  2. 写一个线程,跑步
  3. 线程内部都是for循环到100
    1. 再写一个判断
    2. 每次进来要判断一下
  4. 开启一个线程,取名为兔子
  5. 开启一个线程,取名为乌龟
  6. 打印出winner,终止循环(线程也被终止)

c.实现Callable接口(了解即可)

区别

  1. 实现的接口不一样
    1. 需要返回值
  2. 重写的方法
    1. call
  3. 多了一个开启
  4. 多了一个关闭

代码

  1. 返回值
    1. implements Callable<返回值> //Bollean即可
  2. 开启
    1. 相当于线程池
    2. 创建一个池子
  3. 执行
    1. 提交submit三个线程
    2. 获取结果(返回值,)
  4. 关闭
    1. shutdown掉

Callable好处

  1. 可以定义返回值
  2. 可以抛出异常

并发问题

3. 剖析Thread类

a.静态代理(StaticProxy)

定义

  1. 真实对象/代理对象
  2. 实现同一个接口

这个接口的使用

  1. 代理对象
    1. 直接用
  2. 真实对象
    1. 代理对象调用真实对象(传入真实对象作为参数)
    2. 从构造函数传入即可

好处

  1. 真实对象可以只用作自己的事
  2. 代理对象帮做其他的事

b.Lamda表达式

目的

  1. 为了简化代码
  2. 必须是函数式接口
  3. 是函数式编程

函数式接口

  1. 一个只包含一个抽象方法的接口

简化方法

Lamda表达式

推导

  1. 原始情况
    1. 存在一个 函数式接口
    2. 在某个类中要实现这个接口(实现类)
    3. 调用这个被实现的接口对象(使用类)
    4. 这里明明只用一次,却分成了三个类
  2. 改进:静态内部类
    1. 把这个接口类,放到“使用类”中去实现
    2. 即实现类在使用类之中
    3. 要加static
  3. 再改进:局部内部类
    1. 在使用类的,方法内部,实现
  4. 再改进:匿名内部类
    1. 直接new 接口
    2. 必须借助一个接口
  5. 再改进:用Lamda简化
    1. 省略了new
    2. 直接
      1. 接口名 对象名=()-> 实现代码
    3. 只是省略了写法

书写结构

  1. ()-> 自己的代码
  2. 行数
    1. 如果实现方法中只有一行,会被简化为一行
    2. 如果有多行,会被变成代码块 {}
  3. 接口的方法中,存在多个参数
    1. 括号中放入多个参数

c.回归多线程

使用

  1. new Thread(new Runnable() {} ).start;
    1. 真实类
  2. 真实类和代理类都做事
  3. 但是真实类做核心的事
  4. 是线程的底部实现原理

4. 线程五大状态

线程方法

  1. 改优先级
  2. sleep()
  3. join() 线程终止
    1. 让这个线程的插队,直接执行
  4. yield 礼让别人
    1. 暂停这个线程
  5. 测试线程是否在活动

线程停止

  1. 不推荐的stop()、destroy()方法
    1. 被划线的
    2. JDK不推荐的
  2. 推荐线程自己停下来
    1. 在外部写一个flag标识位,在外部更改
    2. 当flag=false 线程终止运行
    3. 线程内部执行时,先判断flag

进程休眠

  1. 存在异常需要抛出
  2. 每个对象都有个锁,sleep不会释放锁
    1. 保持在这个进程内
  3. 模拟网络延迟
    1. 放大问题的发生概率
  4. 倒计时
    1. 一个数:10
    2. 线程内容为-1
    3. 一秒钟跑一次这个线程

进程礼让

概念

  1. 让当前执行的线程暂停,转为就绪状态,不是阻塞
  2. 不一定成功,
    1. 可能CPU下一个再调度的还是自己
    2. 可能直接执行完了,没来得及礼让

进程强制执行

概念

  1. 阻塞掉其他线程
  2. 使本线程执行

代码

  1. 在main线程的for循环中间
  2. 假如一个thread.join()

观测线程状态

文档

Thread.state

  1. new
    1. 新生
  2. runnable
    1. 运行
  3. blocked
    1. 阻塞
  4. timed waiting
    1. 阻塞
  5. terminated
    1. 终止

用代码观察

  1. 线程内部是一个for循环
  2. new 之后 :get一次
  3. start() 之后 :get一次
  4. 用while监听
    1. 只要不终止
    2. while(终止)
  5. 用sleep间隔监听
  6. 进程终止后不会再启动

线程优先级

概念

  1. 分为1~10
  2. 最小为1,最大为10
  3. 优先级越高,CPU选择时权重越大
    1. 不是一定每次CPU都能选得准高的
    2. 调度策略取决于CPU
  4. 可以用getPriority()
    1. 来获取
  5. setPriority();
    1. 来设置

文档

  1. 大于10或小于1会抛出异常
  2. 会默认给一个级别:5
  3. 主线程默认优先级改不了

守护线程

概念

  1. 线程分为用户线程和守护线程
  2. JVM必须确保用户线程执行完毕
  3. JVM不用等待守护线程执行完毕
    1. 比如垃圾回收、监控内存、记录日志

测试守护线程代码

  1. 定义一个线程
  2. 设置成守护线程 .setDaemon();
  3. 启动线程
  4. 定义一个线程
  5. 启动
  6. 程序结束之后,守护线程也得跑一会

5. 线程并发

线程同步

概念

  1. 同步
    1. 多个线程操作同一个资源
    2. 排队的问题(队列)
    3. 等待池
  2. 并发
    1. 一个对象被多个线程操作
    2. 不安全
  3. 同步就是为了解决并发问题
    1. 使用一个队列
    2. 让他排队
  4. 队列
    1. 光有队列不够,包装安全还需要锁
    2. 每个对象都有一把锁,可以解开可以锁起
    3. synchronized
    4. 追求安全会损失性能
    5. 优先级高的不能等待优先级低的问题
      1. 性能倒置

代码:三大不安全的案例

买票

  1. 定义一个int对象票数
  2. 用外部flag来让线程停止
    1. 如果票数为0
  3. 三个线程,来买票,操作同一个对象
    1. 对于每个线程来说,当时看到的可能都为1
    2. 都拿
    3. 然后就变成-1了

不安全的取钱

  1. 两个线程去取钱
  2. 一个账户类型
    1. 一个int类型作为账户余额
    2. 一个String作为卡号
  3. 取钱对象继承Thread
    1. 信息
      1. 一个账户类型
      2. 取多少钱
      3. 余额
    2. run函数
      1. 判断是否有钱
      2. 算出卡内余额
      3. 算出现在手里的钱
  4. 另一个对象,main函数
  5. main函数
    1. 用sleep来放大问题
    2. 可能取成负数
    3. 当时看到的都是正的

线程不安全的集合ArrayList

  1. 为什么是不安全的
  2. List list= new ArrayList();
  3. 用1000个线程向list中加数据
  4. 然后看一下list.size();
  5. 发现没有1000个内容
  6. 一个顺序,两个线程加数据加在了同一个位置
    1. 被覆盖了

总结

  1. 内存是各自的(工作内存)
  2. 当时看到的都是各自读取下来的
    1. 取钱
    2. 买票
    3. 加数组

如何保证线程安全

  1. 对于对象
    1. get/set方法
  2. 对于方法
    1. synchronized

synchronized关键字使用

同步方法
  1. 每个对象对应一把锁
  2. synchronized的方法,必须获得该对象的锁
    1. 才能执行
    2. 否则会阻塞
  3. 一旦执行
    1. 独占该锁
    2. 直到释放
    3. 后面的进程才能使用
  4. 缺陷
    1. 大大影响效率
    2. 修改时候才需要同步
  5. 代码,直接加在方法前

同步块

原因

  1. synchronized方法锁的范围有限
    1. synchronized方法默认锁住本身的this对象
    2. 想要锁别的类的对象:同步块
  2. synchronized( Obj)
    1. Obj可以是任何对象
    2. 但是推荐为共享资源(需要修改的)
  3. 代码 :synchronized(){}
    1. 需要锁的对象放入()
    2. 相关代码丢入{}中

CopyOnWriteArrayList

JUC

  1. Java.Util.concurrent包
  2. Java的并发包
  3. 有一个关于List安全的记号

测试JUC安全

  1. CopyOnWriteArrayList是线程安全的
  2. 集合都加范型:<>
  3. 不需要加锁了,本身就是安全的

死锁

概念

相互拥有对方的资源,僵持住了

代码

  1. static,保证只有一个
  2. 第一个,synchronized锁住其中一个资源
    1. 用一个时间差 sleep
    2. 锁住另一个资源
  3. 第二个,synchronized锁住其中一个资源
    1. 用一个时间差 sleep
    2. 锁住另一个资源

解决

  1. 不要去锁对面的资源
  2. 破坏必要条件

Lock锁

概念

  1. 显式的锁,区别于synchronized
  2. 相当于Synchronized
  3. 是可重入锁的实现类

代码

  1. 定义一个可重入锁 lock
  2. 加锁 lock.lock
  3. 解锁 lock.unlock

总结

  1. 一般写在try catch中
  2. lock只能锁代码块

6. 线程通信

生产者消费者问题

问题背景

解决思路

  1. 仅有Synchronized不够
  2. p操作方法(等待),v操作方法(唤醒)

解决方法

  1. 管程法
    1. 通过池子
    2. 池子,缓冲区
  2. 信号灯法
    1. 有个标志位
    2. true 等待,false 唤醒

管程法代码-利用缓冲区解决

  1. 生产者
  2. 消费者
  3. 容器
    1. push方法
      1. if满了
      2. 等待 (p操作)
      3. push完,在末尾处
      4. v操作
    2. pop方法
      1. if空
      2. 等待 (p操作)
      3. pop完,在末尾处
      4. v操作

信号灯法-标志位

  1. 生产者
  2. 消费者
  3. 中间的处理函数
  4. if flag为真
    1. 生产者操作
    2. 然后唤醒消费者
  5. if flag为假
    1. 消费者操作
    2. 然后唤醒生产者
  6. 只能交替执行

线程池

概念

经常创建和销毁,开销大。
使用完放入池子,使用时放入池子。
只创建一次,销毁一次

接口

  1. ExecutorService
    1. 真正的线程池接口
    2. 执行
    3. 关闭
  2. Executor
    1. 工厂类
    2. 用于创建线程池

代码

  1. 只要记住这两个类
  2. ExecutorService es=Executor.newFixedThreadPool
    1. 工厂类,用打点创建
  3. 丢入池子
    1. execute,无返回值
    2. submit,有返回值
  4. 关闭

多线程进阶 JUC

准备工作

官方文档,(学习)

环境

1.基础回顾

进程状态wait和sleep的区别

  1. 来自于不同的类
  2. wait会释放锁,sleep不会释放锁
  3. wait必须在同步代码块才能用,sleep随处可用
  4. wait不需要捕获异常,sleep需要

Lock锁

可重入锁:可以多次获取同一个锁,也必须多次释放

  1. Lock是一个接口
    1. 有三个实现类
      1. 可重入锁
      2. 读锁
      3. 写锁
  2. 公平锁/非公平锁
    1. 可重入锁
      1. 无参构造:非公平锁
      2. 有参构造,可以传入公平/非公平
    2. 公平锁
      1. 必须先来后到
    3. 非公平锁
      1. 可以插队
      2. 根据CPU来
      3. 默认是非公平锁,交给CPU调度,速度更快
  3. 使用步骤
    1. new 一个锁
    2. 加锁
    3. 写锁 放在finally里
  4. 而Synchronized是自动的

Lock锁和Synchronized区别

  1. Synchronized是内置的关键字,Lock是个类
  2. Synchronized不能判断是否获取到锁,Lock可以
  3. Synchronized会自动释放锁,Lock必须手动解锁
    1. 不释放会出现死锁
  4. Synchronized 线程1占有锁,线程2会一直等
  5. Lock可以尝试获取锁
    1. 不会傻傻等
    2. 等不到就结束
  6. Synchronized是关键字,很多东西不能改
    1. 是可重入锁
    2. 不可以中断
    3. 非公平
  7. Lock自由度更高
    1. 以上三个都可以自己设置
  8. 适用范围
    1. Synchronized适合少量的代码
    2. Lock适合大量的代码

生产者和消费者问题

  1. 两个线程操作同一个变量,临界资源,AB线程合作,产生通信

虚假唤醒

  1. 多个线程同时被唤醒时,
    1. 其中一个线程可能改变了唤醒条件
    2. 使另一个线程唤醒条件不满足了
    3. 但是另一个却已经被唤醒了
  2. 使用if()时,进去就不会停
  3. 换成while(),另一个就会等待

Lock版的生产者消费者问题

  1. 在Synchronized版时
    1. P操作时wait
    2. V操作是notify
  2. Lock版
    1. new一个Condition
      1. Condition condition=lock.newCondition
    2. await
      1. condition.await
    3. signal
      1. await.signal

Condition优势

  1. 线程是一个随机状态,没办法做到
    1. 线程A->B->C->D
  2. 如何精准控制被唤醒线程
    1. condition2.signal();
    2. 唤醒condition2的线程
    3. signal是通知自己
    4. Synchronized版中的notify是通知他人

2. 锁是什么,如何判断锁的是谁。8个锁的问题

锁的对象

  1. 锁只能锁
    1. 整个Class出来的全部东西
    2. 锁单个对象

暂停

  1. 使用TimeUnit.SECONDS.sleep();

Synchronized锁时

1.标准情况下,两个线程都加了Synchronized关键字

  1. 两个线程,为什么执行顺序是固定的了?
    1. 不是因为先调用的原因
    2. 是因为锁的存在
      1. 两个线程用了同一把锁
      2. 谁先拿到谁执行

2.其中一个线程,延迟4秒执行

  1. 执行顺序还是固定的
    1. 不是因为先调用的原因
    2. 是因为锁的存在
      1. 两个线程用了同一把锁
      2. 谁先拿到谁执行

3.增加了一个普通线程。另外两个线程用了一把锁

  1. 普通方法没有用到锁

4.new了两个对象时

  1. 按时间来
  2. 两个对象
    1. 不是同一把锁

5.两个对象,两个线程加了static静态方法

  1. static造成在类刚加在的时候就有了锁
  2. 锁住的是整个class
  3. 锁住的是整个模版
    1. 两个对象用来一把锁

6.一个对象,调用一个static锁线程和一个没加static锁的线程

  1. static锁线程
    1. 锁的是类
  2. 没加static锁的线程
    1. 锁的是调用者
  3. 不是同一个目标
    1. 所以不是同一把锁
  4. 所以看CPU调度谁先谁后

7.两个对象,每个都调用一个static锁线程和一个没加static锁的线程

  1. 共4个,全不是同一把锁

小结

  1. 锁的对象
    1. new出来的
      1. 具体的一个对象
    2. static
      1. 锁住的一个模版

https://www.cnblogs.com/itiaotiao/p/12651573.html

3. 集合类不安全

List不安全

ArrayList

  1. 单线程下的确安全
  2. 多线程下不安全
    1. 代码
      1. 在for循环中
      2. 每次循环,new一个线程放入一个值
    2. 出错了,并发修改异常
    3. 解决方法,加锁
      1. 直接不用ArrayList,换成Vector
      2. 用工具类 Collections.synchronizedList(new )
      3. 用CopyOnWriteArrayList

CopyOnWriteArrayList

  1. 写入时复制技术COW
  2. 读取时是没问题
  3. 写入时防止覆盖
    1. 写入时复制一份
    2. 复制完给调用者
    3. 调用者在复制的数组中写
    4. 用复制出来的替换原来那个
  4. 写入时不发生覆盖

CopyOnWriteArrayList比Vector好在哪

  1. Vector有Synchronized,是通过锁实现的
    1. 效率低
    2. 从源码看到的
  2. CopyOnWriteArrayList是复制
    1. 从源码看到的

Set不安全

本质和List是一样的,都是从Collection而来

  1. 同样多线程下不安全
    1. 代码
      1. 在for循环中
      2. 每次循环,new一个线程放入一个值
    2. 解决办法
      1. 工具类
      2. 写入时复制,CopyOnWriteArraySet

HashSet底层是什么

  1. 底层是HashMap
  2. key部分不重复
    1. 一个if语句
    2. 如果那个key!==null
    3. 就插入失败

Map不安全

  1. HashMap是怎么用的
    1. new的时候要填加载因子和初始化容量
  2. HashMap默认等价于什么
    1. 加载因子:默认0.75
    2. 初始化容量:默认16
  3. 原因
    1. 16: 位运算,2的4次方
  4. 不安全的代码测试
    1. 每次put进去的时候都new一个线程来put
  5. 解决方法
    1. Collections工具类
    2. 没有CopyOnWrite了
      1. 使用的是ConcurrentHashMap

ConcurrentHashMap

  1. 和写时复制有区别
  2. 1

3. JUC下剩下的几个类

Callable

为什么需要

  1. 多线程的创建方式
    1. 重写的方法不同
  2. 有返回值
  3. 可以抛出异常
  4. Runnable太简单

使用介绍

  1. 泛型的参数 = 方法的返回值类型
  2. 线程的启动方式有且只有一个 .start()
    1. Thread只能启动runnable
  3. 如何启动Callable
    1. 通过相互调用的关系
      1. 适配类
    2. runnable
    3. FutureTask可以相当于runnable
    4. FutureTask的参数是callable的实现类
  4. 返回值
    1. 返回结果在FutureTask中
    2. FutureTask对象的get的方法
      1. 可能会阻塞
      2. 异步通信
  5. 有缓存
    1. 执行两次相同的
      1. 只输出了一次结果
      2. 可以提高效率

CountDownLatch

减法计数器

文档

  1. 辅助工具类
  2. 允许一个或者多个线程等待
  3. 用来计数

代码

  1. new一个对象
    1. 倒计时一个int对象
    2. 使用方法,可以-1
      1. 每次一个操作完成之后可以-1
    3. 等待归零之后再向下执行
      1. 打点.await();
      2. 归零之后才会从此处执行,向下走

CyclicBarrier

加法计数器

代码

  1. new出来一个对象
    1. 参数为最终结果
  2. 每次执行完一个线程,就会+1
    1. 不需要使用方法,自动+1
  3. await方法,达到指定值才会向下执行

Semaphore

相当于一个通行证,先来后到,用来限制线程数量,限流

代码

  1. new一个对象
    1. 参数为信号量数量
    2. 用来限制线程数量
  2. 信号量-1
    1. .acquire()
  3. 信号量+1
    1. .release()

2. 剩下的一个锁

读写锁

概念

  1. 读可以被多个线程拿到(共享锁)
    1. 读的时候不能被写打扰
  2. 写只能给一个线程(独占锁)

代码

  1. 自定义缓存
    1. 使用Map来存储
    2. put方法加一个数据
      1. 加前打印:写入
      2. 加后打印:写入完成
    3. get方法减一个数据
      1. 读前打印:读取
      2. 读后打印:读取入完成
  2. 没有加锁的情况下
    1. 分别使用5个线程写入
    2. 再使用5个线程读取
    3. 发现读和写是无序的
  3. 加读写锁
    1. 声明
      1. 读写锁的接口 读写锁名=new 实现类();
    2. 加锁
      1. 加写锁
        1. .writelock().lock();
      2. 加读锁
        1. .readlock().lock();
    3. 解锁
  4. 假如是纯锁lock
    1. 限制太大

4. 阻塞队列

阻塞队列 BlockingQueue

概念

  1. 队列
    1. 先进先出
  2. 阻塞
    1. 写入
      1. 队列满了时
    2. 读取
      1. 队列为空时

文档

  1. 父类Collection
  2. 实现类
    1. 同步队列
    2. 链表队列
何时使用
  1. 多线程
  2. 线程池
其他队列

BlockingDeQeue 双端队列
AbstraQueue 非阻塞队列

四组API(四个方法)

  1. 抛出异常
  2. 不会抛出异常
  3. 阻塞 等待
  4. 超时 等待

使用

  1. new时要填参数
    1. 队列大小
  2. 抛出异常的API
    1. 出错了,会报异常
    2. 满了时,还add
    3. 空了之后,还move
    4. 查看队首的元素,.element()
  3. 不会抛出异常的API
    1. offer来存,poll返回
      1. 成功返回true
    2. 满了之后还加,会返回false
    3. 空了之后还poll,返回null
    4. 查看队首的元素,peek()
  4. 阻塞 等待的API
    1. 阻塞时一直等待
    2. 使用
      1. 加 .put()
      2. 取 .take()
  5. 超时 等待的API
    1. 阻塞时等待超过一定时间就不等了
    2. 使用
      1. 使用不抛异常的方法,再加参数
      2. offer(“D”,等待时间,等待时间的单位)
      3. poll(等待时间,等待时间的单位)

同步队列 SynchronousQueue

没有容量。进去一个元素之后,必须取出来才能再放下一个

代码

  1. new 是阻塞队列的实现类
  2. 一个线程
    1. 放入 put()
  3. 另一个线程
    1. 拿取 take()
  4. 很稳定
  5. 和其他的BlockingQueue 不同

5. 线程池

概念

池化技术

  1. 程序运行的本质
    1. 占用系统资源
  2. 优化资源的使用
    1. 池化技术
  3. 概括池化技术
    1. 事先准备好一些资源,有人要用就来拿
    2. 避免返回拿取和归还

线程池的好处

  1. 降低资源的消耗
  2. 从而提高响应效率
  3. 方便管理
  4. 总结
    1. 线程复用
    2. 控制最大并发数
    3. 管理线程
  5. 考点
    1. 三大方法
    2. 七大参数
    3. 四种拒绝策略

代码来了解

  1. Executors.
    1. 单线程池
    2. Fixed,固定线程池
    3. Cached,可伸缩线程池
  2. 使用线程池创建
    1. 新建:用ExecutorService es=
    2. 使用:es.execute(线程对象)
    3. 关闭:shutdown,放到finally里
  3. 单线程池
  4. Fixed
  5. Cached

七大参数

文档

  1. 开启线程的本质是 ThreadPoolExecutor
    1. 有7个参数
      1. 核心线程池大小(银行营业窗口)
      2. 最大线程池大小(银行总窗口,营业窗口+关闭窗口)
      3. 超时时间,会自动释放
      4. 超时单位
      5. 阻塞队列(银行等待区)
      6. 线程工厂,创建线程,不需要修改
      7. 拒绝策略
    2. 最好通过本质去创建

详细介绍

举例
  1. 单线程池

    1. 核心线程池大小:1
    2. 最大线程池大小:1
  2. Fixed

    1. 核心线程池大小:固定值
    2. 最大线程池大小:同
  3. Cached

    1. 核心线程池大小:0
    2. 最大线程池大小:21亿个
概念
  1. 核心线程池
    1. 永远是开着的线程
  2. 最大线程池
    1. 线程太满了,超过一定值
  3. 超时等待
    1. 时间+时间单位
    2. 超过时间之后会关闭线程池
  4. 阻塞队列
    1. 设置触发最大线程池的等待线程数
  5. 线程工厂
  6. 拒绝策略
    1. 4种
      1. 报出异常
      2. 哪来的去哪里,让别的线程执行它,比如main线程
      3. 不抛出异常,丢掉任务
      4. 不抛出异常,尝试和最早的竞争

代码,使用七大参数

  1. ExecutorService es=ThreadPoolExecutor(,)

CPU密集型和IO密集型

    1. 可以同时执行的线程数
  1. 最大最大线程池数该如何定义
    1. CPU密集型
      1. 几核,就是几
    2. IO密集型
      1. 要大于核数
      2. 判断IO密集型线程数目
      3. 一般为IO密集型线程的两倍

6. Java新特性

  1. Java新特性中要掌握的
    1. Lamda表达式
    2. 函数式编程/链式编程
    3. 函数式接口
    4. Stream流式计算
  2. 函数式接口
    1. 只有一个方法的接口

函数式接口

函数型接口 Function

文档
  1. 泛型
    1. 传入一个参数T,返回一个类型R
  2. new 一个匿名内部类
  3. 方法中,
    1. 传入的参数是T
    2. 返回的参数是R
总结
  1. new一个对象
  2. 有一个传入参数,有一个返回参数
  3. 用Lamda表达式简化
    1. (str)->{ return str;

断定型接口 Predicate

  1. new一个对象
  2. 只有一个输入参数
  3. 返回值是bool类型
    1. 配合if()使用, return true/false;

消费型接口 consumer

  1. 只有输入参数,没有返回值
  2. 用Lamda表达式简化

供给型接口 apply

  1. 没有输入,只有返回
  2. 用Lamda表达式简化

Stream流计算

概念

  1. 所有的东西无非两种
    1. 存储
    2. 计算
  2. 存储
    1. 集合 Collection
    2. MySQL
  3. 计算
    1. 交给Stream流来操作

文档

  1. Stream下,有很多流的方法
  2. 将list转换成流
    1. list.Stream()
    2. 就变成stream对象了
  3. filter
    1. 断定型接口
      1. 传入一个对象返回true/false
      2. ()->{}中,可以省略掉()
    2. 如果参数中
      1. 断定型接口返回是true
      2. 就返回原对象
  4. forEach
    1. 消费型接口
      1. 不需要参数
      2. ()->{}中,可以省略掉:()->
  5. map
    1. 可以转换
    2. 是一个映射
    3. 函数型接口
      1. 有一个传入
      2. 有一个返回
        1. 转换为大写:toUpperCase()
  6. 排序 sorted
    1. 参数:comparator
    2. 是函数型接口
      1. 两个参数
      2. 使用排序方法:compareTo

链式编程

  1. 连续打点调用,一大行
  2. 为了方便阅读,换行

7. 分支合并

ForkJoin

概念

  1. 并行执行时,提高效率
    1. 一个线程并发成多个
  2. 把大任务拆分成小任务
  3. 合并汇总结果

特点

  1. 工作窃取
    1. 概念
      1. 自己线程的任务执行完了
      2. 别的线程的任务还没做完
      3. 把别的线程的任务拿过来做
      4. 能够加快速度
    2. 如何实现
      1. 双端队列

求累加和

思路
1. 设定一个值
1. 如果小于该值,就正常计算 +=
1. 如果大于该值,就使用分支合并
1. 感觉类似于递归

实现

文档
  1. 使用ForkJoinPool来执行
    1. 构造方法里
    2. 打点,执行:.execute(参数)
      1. 参数是一个ForkJoinTask
      2. 是一个任务
  2. 需要定义一个任务
    1. 两个子类
    2. 递归事件
      1. 没有返回值
    3. 递归任务
      1. 有结果,有返回值
  3. 本题是有返回值
代码
  1. 新建任务
    1. 新建一个类,继承递归任务类,
    2. 泛型是返回值
  2. 实现接口中的方法
  3. 如果小于该值
    1. 直接+=
  4. 如果大于该值
    1. 计算出中间值(二分法)
    2. 再new一个本类的对象(递归)
    3. ForkJoinTask对象
    4. 该对象
      1. fork方法:把任务压入线程队列,拆分
      2. join方法:合并结果
        1. 相当于叶子与叶子的相加
  5. 使用总结
    1. new 一个 ForkJoinPool对象
    2. new 一个 ForkJoinTask对象
    3. 用ForkJoinPool对象
      1. .execute(task) 无结果
      2. submit(task) 有结果

使用并行流,把计算交给Stream流

  1. 使用LongStream
    1. rangeClosed
      1. ( ]
    2. range
      1. ( )
    3. parallel()
      1. 并行计算
    4. reduce()
      1. 从流中生成一个值
      2. 按照参数中的模型

异步回调

Future

  1. 为了对将来的某个结果进行建模

异步调用

  1. 服务器和客户端:Ajax
  2. 发起一个请求
    1. Future类的增强类CompletableFuture
    2. 泛型,返回值,可以为void
    3. 异步执行
    4. 成功会回调
    5. 失败也会回调
  3. new出对象
  4. 打点,runAsync( runnable )执行
    1. 也可以Executor执行
  5. 获取结果
    1. 打点,get()
  6. 任务处理
    1. 打点
    2. 任务成功 :wenComplete
      1. Consumer型接口,两个参数
      2. 任务成功执行
        1. 第一个是正常的返回值
        2. 第二个是null
      3. 任务失败
        1. 第一个是null
        2. 第二个是错误信息
        3. 跳转到任务失败的函数来执行
    3. 任务失败:exceptional
      1. function型接口,有输入有输出
      2. 参数是 Execeptio e
      3. 输出和自己定 return 233

Volatile

概念

  1. 是一个关键字
  2. 是一个轻量级的同步机制,没有Synchronized重

特性

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

保证可见性,通过JMM

什么是JMM

  1. 是一个概念,一个约定
  2. Java内存模型
  3. 为了保证内存安全

哪些具体的约定

  1. 线程解锁前
    1. 把临界变量立刻从工作内存刷回主存
  2. 线程加锁前
    1. 读取主存中的内存,拷贝到工作内存
  3. 加锁和解锁必须是同一把锁(不是8锁问题)
  4. JMM就是工作内存和主内存操作的一组规定
    1. 线程中包括工作内存
    2. 和执行引擎

主存和工作内存之间的八种操作

主存 中间变量 工作内存 执行引擎

  1. read 主存向工作内存读取过程中,先到中间变量处
  2. load 中间变量,再向工作内存运送数据
  3. use 工作内存向执行引擎送数据
  4. assign 执行引擎向工作内存送数据
  5. write 工作内存向主存送数据过程中,经过中间变量
  6. store 中间变量向主存送数据
  7. lock 整个过程中的加锁操作
  8. unlock 整个过程中的解锁操作
  9. 以上8个,4组 - 必须成对使用

存在的问题

  1. 线程A读取了主内存中的变量flag=true
  2. 随后线程B将主内存中flag改为false
  3. 线程A没有获取到最新的flag,怎么办

代码演示

  1. 设定num=0
  2. 一个线程A
    1. 当num=0就不停下来
  3. 另一个线程B
    1. 把num设为1
  4. 及时当num=1了,第一个线程仍然没停下来

解决办法

  1. 主内存中num值发生变化之后要通知线程A
  2. 加上volatile

验证volatile的特性

可见性

  1. 加上volatile之后,主存的变化工作内存能看到

不保证原子性

  1. 原子性:执行过程中不能被打扰
    1. ACID原则
  2. 用20000个线程向里面加值
    1. 判断是否执行完
    2. 当前线程数大于2
      1. main线程和gc线程一直在执行
  3. synchronized可以保证值最后是20000
  4. volatile不能保证
  5. 为什么会被覆盖
    1. num++不是一个原子操作
    2. 别的线程可能会参与进来
  6. 如何能保证原子性(不使用synchronize和lock)
    1. 使用原子类
      1. 把int类型改成原子的int
      2. AtomicInteger
      3. 不使用++,
      4. 把++换成 .getndIncrement()
        1. 使用了cpu底层的CAS
        2. unsafe类
        3. 和操作系统底层相关

指令重排

  1. 禁止指令重排,测不出来
  2. 什么是指令重排
    1. 计算机并不是按照你写的代码的顺序执行的
    2. 编译器会对代码优化
    3. 指令并行也可能会重排
    4. 内存系统也可能重排
  3. 指令重排可能会对代码造成影响
    1. 多线程影响赋值操作的顺序

如何避免指令重排的

  1. 内存屏障、就是CPU执行的指令
    1. 保证操作的执行顺序
    2. 保证某些变量的内存可见性(内存可见性是通过此来实现)
  2. 如何保证的屏障
    1. 正常情况下
      1. 一个线程来读
      2. 一个线程来写
    2. 加了volatile
      1. 就会在工作内存上面和下面加了一个内存屏障
      2. 使得每次工作内存读要从主存读最新的
      3. 写也是主存最新的
  3. 在单例模式中用的最多

8. 单例模式

  1. 只要是单例模式
  2. 构造函数必须私有

饿汉式

概念

  1. 在类加载时就会实例化一个对象
  2. private保证对外不可见,然后getInstance
    1. static方法

存在的问题

  1. 可能会浪费内存
    1. 一上来就会加载进内存
    2. 而此时可能还没使用

懒汉式

概念

  1. 在使用时才会加载一个对象

在单线程是没问题,但多线程不安全

  1. 启动十个线程
  2. 都getInstance
  3. 导致启动了多个实例
    1. 每个线程执行到if判断时
    2. 拷贝到自己的工作空间
    3. 当时的instance都为null

解决方法

  1. 当==null时,
    1. 加一个Synchronized锁
    2. 锁住这个类
    3. 保证只有一个
    4. 在锁中再判断一次
  2. 双重检测锁模式
    1. 为什么不在getInstance方法上加锁
    2. Synchronized锁的准备时间太长
      1. 每次到了都要准备一下
    3. 把它放在if里面,避免Synchronized锁的准备
  3. new的操作不是原子性
    1. 分配内存空间
    2. 执行构造方法,初始化对象
    3. 将对象指向内存空间
  4. 启动一个线程时候有可能执行了132
    1. 线程A还没完成2时
    2. 导致线程B直接以为对象非空了
    3. 直接return了
    4. B帮A return了

静态内部类

  1. 在一个类里面再写一个内部类
    1. 内部类是static的
  2. 在内部类里面new对象
  3. 再弄一个getInstance
    1. return那个内部类
  4. 是不安全的

反射

  1. 只要有反射,任何单例都不安全
  2. 先获得一个单例
  3. 通过反射破坏单例
  4. Constructor<类型> cs=.class.getDeclaredConstrator(null);
    1. 获得空参构造器
  5. 通过反射来创另一个对象,又破坏了

解决这种破坏

  1. 在构造函数里加一个Synchronized锁
  2. 升级成三重检测
  3. 两个对象都是用反射创建的
    1. 又被破坏了

再尝试解决

  1. 再定义一个变量作为标志位
  2. 判断条件变成了标志位+防止破坏
  3. 标志位也可能被破坏
  4. 通过枚举类型

通过枚举类型防止单例被破坏

  1. 枚举自带单例模式
  2. 枚举本身也是个类
  3. 枚举一个对象,就是单例类
  4. 用getInstance来获取

枚举类型也有一点问题

  1. 提示枚举当中没有无参单构造方法
    1. javap
    2. jad反编译
  2. 发现枚举过程中只有一个有参的构造器

9. CAS

概念

  1. 原子类的底层用了CAS
  2. 原子类对象打点: .compareAdnSet()
    1. CAS即compareAdnSet 即 比较并交换
  3. 两个参数
    1. expect
    2. update
  4. 如果值是expect,就把它改为update
    1. 和期望(expect)相同,就改为update

++的底层

  1. ++即是getAndIncrement
  2. 是getAndInt的返回值,getAndInt是unsafe类打点调用的
  3. getAndInt的参数
    1. this:当前对象
    2. valueOffSet

unsafe类

概念

  1. Java无法直接操作内存
  2. C++可以操作内存
  3. Java调用C++可以通过native
  4. unsafe是java的后门,可以帮助操作内存

方法和对象

  1. valueOffSet=unsafe.objectFieldOffset
  2. unsafe.objectFieldOffset
    1. 是内存地址的偏移值
  3. value通过volatile实现
    1. 避免指令重排
    2. 可以被隔离

总结++

  1. ++方法是getAndIncrement中的getAndInt
  2. getAndInt参数
    1. ths:本对象
    2. 当前对象内存的值
    3. 1
  3. getAndInt方法内部
    1. 定义了var5
    2. 获取内存地址的值
    3. 先获取内存地址偏移量
    4. 两个相加就是当前对象的值,赋值给var5
  4. 然后调用“比较并交换方法”
    1. 如果当前对象的内存地址偏移值就是还是当前对象的值
      1. 相当于复核查一下
    2. 就把它内存地址的值 + 1
      1. 1就是传进来的第三个参数
  5. 比较并交换方法 ,是while内的
    1. 无限循环下去
    2. 自旋锁

总结CAS

  1. CAS是比较,并交换工作内存中的值和主存中的值
    1. 如果是期望的,就执行操作
    2. 不是,就一直循环,阻塞住了
  2. 有三个操作数
  3. 好处:自带原子性,CPU层面的,所以快
  4. 缺点:
    1. 会循环,会耗时
    2. 一次性只能保证一个变量的原子性(够了)
    3. 存在ABA问题

ABA问题

概念

  1. 两个线程去操作同一个变量
  2. A期望值是1
  3. B速度快,过来把1改成2,然后再把2改成1
  4. 对于A来说,A不知情,以为全部没变
  5. 我们希望谁动过值,要告诉我们

代码

  1. 捣乱线程B,改值,再改回来
  2. A执行期望的值,如果是,就update

SQL中

  1. 上一把乐观锁
  2. 只要判断锁没动过,再改值

解决办法:原子引用

  1. 带版本号的原子操作
  2. 有个类来帮助使用原子引用
  3. 不使用原子类,而使用原子引用的类
    1. 两个参数
    2. 初始值
    3. 初始的版本号时间戳
      1. 为1
      2. 每次动过就+1
代码
  1. B线程操作,A要知道
  2. 线程每次进入先获取版本号
  3. B每次修改时,用CAS
  4. CAS方法操作,加了两个新的参数
    1. 期望的版本号,
    2. 新的版本号
  5. A再改时,版本号不一致,无法CAS
    1. 修改失败
  6. 引入 原子引用,可以解决ABA机制
    1. 同乐观锁的机制

10. 各种锁的理解

公平锁,非公平锁

概念

  1. 是否可以插队
  2. 默认非公平锁
  3. 修改成公平/非公平
    1. 参数

可重入锁

概念

  1. 又叫递归锁
  2. 拿到了外面的锁就自动拿到里面的锁

代码

  1. 一个加锁的方法,调用另一个加锁的方法
  2. 在外面的锁进入之后,
    1. 不需要再获得内部的锁
  3. Lock锁必须配对,不然会锁死在里面

自旋锁

概念

  1. 不断循环,等待
  2. 直到成功

代码

  1. 自己写一个锁,使用原子类型和CAS
    1. 线程进入之后,打印线程名
    2. 使用原子类型对象
    3. 加锁
      1. while循环 ,内部是CAS,期望值为null
    4. 解锁
      1. 如果是想要的值
      2. update为null
  2. t1加锁
    1. t2也进入,自旋
    2. t1结束自旋,解锁后
    3. t2才能解锁

死锁排查

概念

  1. 两个人互相抢夺资源
    1. 互相占用了对方的资源
    2. 试图获取对方的资源

代码

  1. 第一个线程
    1. Synchronized(lockA)中
    2. 嵌套
    3. Synchronized(lockB)
  2. 第二个线程
    1. Synchronized(lockB)中
    2. 嵌套
    3. Synchronized(lockA)

四要素

如何排查死锁

  1. 看日志
  2. Jdk/bin下的很多工具
  3. jps定位进程号
    1. jps -l : 定位进程号
    2. jstack + 进程号:查看堆栈信息
  4. 堆栈信息
    1. Found 1 deadlock
    2. 详细信息

其他锁

  1. 读写锁(独占锁、共享锁)
  2. 乐观锁悲观锁(MySQL)

你可能感兴趣的:(笔记,java,服务器,linux)