复习多线程相关知识
Posted 满眼*星辰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了复习多线程相关知识相关的知识,希望对你有一定的参考价值。
多线程
操作系统相关
冯诺依曼体系结构
输⼊单元:包括键盘,⿏标,扫描仪,写板等
寄存器:内存
中央处理器(CPU):含有运算器和控制器等
输出单元:显示器,打印机等
总结:所有设备都只能直接和内存打交道
进程
概念
系统分配资源的最⼩单位,⼀个任务就是⼀个进程
进程的组成
- PID:进程的唯⼀身份标识。
- 进程状态包括: 新建状态 就绪状态 运⾏状态 阻塞状态 销毁状态
- 优先级:决定进程的执⾏顺序。
- 记账信息:为了保证进程执⾏的相对公平。
- 上下⽂:保存本次的执⾏状态,以便下次继续执⾏,整个过程就称之为⼀个上下⽂。
- ⼀组内存:指定进程需要使⽤的资源。
进程状态
- 就绪:进程处于可运行的状态,只是CPU时间片还没有轮转到该进程,则该进程处于就绪状态。
- 运行:进程处于可运行的状态,且CPU时间片轮转到该进程,该进程正在执行代码,则该进程处于运行状态。
- 阻塞:进程不具备运行条件,正在等待某个事件的完成
时间片
任务执⾏的⼀⼩段时间叫做时间⽚,任务正在执⾏的状态叫做运⾏状态,任务暂停叫做就绪状态,就绪状态的任务要等待下⼀个时间⽚。
并发与并行
- 并发:多个进程在一个CPU下采用时间片轮转的方式,在一段时间之内,让多个进程都得以推进, 称之为并发(只有一个资源,轮流执行就叫并发运行)
- 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行(所有的应用一起运行,就叫并行运行)
内核态与用户态
- 操作系统内核作为直接控制硬件设备的底层软件,权限最高,称为内核态,或核心态。
- 用户程序的权限最低,称为用户态
线程
概念
系统执⾏的最⼩单位,它被包含在进程之中,是进程中的实际运作单位。
线程优势
线程的创建和消耗⽐进程成本低很多,效率更⾼,⽽且同⼀个进程中的多个线程是可以共享资源⽂件的,⽽进 程和进程之间是不能共享资源的,因此这就是线程的诞⽣的意义。
共享资源
线程间可以共享的资源:
- 内存可以共享
- 打开的⽂件可以共享
线程间不可⽤共享的资源:
- 线程的上下⽂、状态、优先级、记账信息不共享。
- 每个线程有⼀个栈空间不共享。
进程 vs 线程
1.进程是资源分配的最小单位,线程是系统调度的最小单位
2.一个进程中至少包含一个线程,线程是依附线程存在的,而进程的实际执行单位是线程
3.进程之间不能共享资源,而线程之间可以共享资源
线程的创建方式
- 继承 Thread 重写 run ⽅法实现;
- 实现 Runnable 接⼝重写 run ⽅法实现;
- 实现 Callable 接⼝重写 call ⽅法实现。
线程的构造函数
- 定义线程名称
- 定义线程分组
- 定义线程的任务
常见属性
- 定义优先级(5) 1-10
- 设置线程名称
- 定义线程类型
a)守护线程
b)用户线程
常用方法
- 启动 start / run
- 休眠 sleep
- 等待执行完 join
- 线程通讯 wait / notify / notifyAll | LockSupport park / unpark
- 出让cpu执行权:yiled
注意事项:
a)wait 要配合 synchronized 使用
b)wait 和synchorized 必须要保证是一个对象
start VS run
- run 为普通⽅法,start 启动线程的⽅法;
- run 可以执⾏多次,⽽ start 只能执⾏⼀次;
- start 会开启新线程执⾏,⽽ run 使⽤当前线程执⾏。
wait VS sleep
⼆者的相同点:
- 都可以让线程休眠;
- 都可以响应 interrupt 的响应。
⼆者的不同点:
- wait 必须在 synchronized 中使⽤,⽽ sleep 却不⽤;
- sleep 是 Thread 的⽅法,⽽ wait 是 Object 的⽅法;
- sleep 不释放锁,wait 释放锁;
- sleep 有明确的终⽌等待时间,⽽ wait 有可能⽆限期的等待下去;
- sleep 和 wait 产⽣的线程状态是不同的,sleep 是 TIMED_WAITING 状态,⽽ wait 是 WAITING 状态。
线程终止
- 使用全局变量
- 使用 interrupte() 终止线程
- stop 【已过时】
isInterrupted VS interrupted
- interrupted:静态⽅法,判断当前线程的中断标志位是否设置,调⽤后清除标志位;
- isInterrupted:实例⽅法,判断对象关联的线程的标志位是否设置,调⽤后不清除标志位。
线程状态图
线程安全问题
导致线程不安全的原因
- ① 抢占式执⾏
- ② ⾮原⼦操作
- ③ 内存可⻅性
- ④ 指令重排序
- ⑤多个线程同时修改同⼀个变量
线程安全解决方案
- volatile :解决内存可见性问题和禁止指令重排序
- 加锁
a)synchronized
b)Lock - ThreadLocal
synchronized
实现原理
- 在操作系统层面来说,它是使用互斥锁来实现的。
- 在JVM层面来说,使用 synchronized 修饰代码的时候会在编译之后在代码的前面和后面加上监视器锁。
- 在 Java 层面来说,他就是将锁信息存在对象头中,每次判断线程 id 和对象头中的锁 id 是否相同,相同则表示为拥有者。
执行流程 / 锁优化
(JDK1.6 后)
四个阶段:
- 无锁:在没有人访问的时候是无锁的
- 偏向锁:第一次有人访问,升级为偏向锁,在对象头中把线程 id 记录到对象头中
- 轻量级锁:如果再次访问的对象不是锁的拥有者,则自旋变为轻量级锁
- 重量级锁:多次自旋还没有得到锁,升级为重量级锁
Lock
lock.lock();
try
finally
lock.unlock()
- lock 放在 try 上⾯;
- finally ⾥⾯⼀定要释放锁。
synchronized VS Lock
- synchronized 是 JVM 提供的解决方案
- synchronized 和 Lock 修饰的代码块范围不同,Lock 只能修饰代码块,⽽ Synchronized 可以修饰⽅法、静态⽅法、代码块;
- synchronized 无需是手动加锁和释放锁,而 Lock 需要
- synchronized 是非公平锁,而 Lock 既可以是公平锁也可以是非公平锁
死锁
定义
死锁是指两个或两个以上的线程在执⾏过程中,由于竞争资源或者由于彼此等待⽽造成的⼀种阻塞的现 象。
条件
- 互斥条件:⼀个资源只能被⼀个线程占有,当这个资源被占⽤之后其他线程就只能等待。
- 请求拥有条件:线程已经拥有了⼀个资源之后,有尝试请求新的资源。
- 不可剥夺条件:当⼀个线程不主动释放资源时,此资源⼀直被拥有线程占有。
- 环路等待条件:产⽣死锁⼀定是发⽣了线程资源环形链。
解决方案
通过破坏条件 ② 或者 条件 ④ 来解决死锁的问题
线程池
使用池化技术来管理线程
优点
- 线程数量和任务数量可控
- 线程池可以友好的拒绝任务
- 可以复用线程
- 线程池拥有更多功能,比如执行定时任务。
创建方式
- 创建固定线程数的线程池;
- 创建带缓冲的线程池;
- 创建延迟任务线程池;
- 创建单个延迟任务线程池;
- 创建单个线程池;
- 根据当前 CPU 和任务创建线程池(JDK 1.8);
- 原⽣创建线程池的⽅式 ThreadPoolExecutor;
ThreadPoolExecutor
七大参数
- 核心线程数
- 最大线程数
- 最大存活使劲啊
- 存活时间的时间单位
- 任务队列
- 线程工厂(设置优先级、命名规则等)
- 拒绝策略
执行流程
添加一个任务
- 核心线程数未满 —— 创建一个线程执行任务
- 检测任务队列未满 —— 将任务存放到队列
- 最大线程数是未满 —— 创建线程执行任务
- 拒绝策略
拒绝策略
JDK 提供 4 种
- 默认执行策略,不执行任务抛出异常
- 使用主线程执⾏来执行任务
- 放弃任务
- 放弃最老任务
自定义拒绝策略
线程池的状态
- RUNNING -> SHUTDOWN:当调⽤了 shutdown() 后,会发⽣这个状态转换,这也是最重要的;
- (RUNNING or SHUTDOWN) -> STOP:当调⽤ shutdownNow() 后,会发⽣这个状态转换,这下 要清楚 shutDown() 和 shutDownNow() 的区别了;
- SHUTDOWN -> TIDYING:当任务队列和线程池都清空后,会由 SHUTDOWN 转换为 TIDYING;
- STOP -> TIDYING:当任务队列清空后,发⽣这个转换; TIDYING -> TERMINATED:这个前⾯说了,当 terminated() ⽅法结束后。
ThreadLocal
线程级别的私有变量
常⽤⽅法
- set
- get
- remove
- initialValue
- withInitial
典型使⽤场景
- 解决线程安全问题;
- 线程级别的数据传递。
注意事项
- ThreadLocal 使用的时候必须要进行 remove ,否则会造成脏读、内存溢出的问题
- 如果 ThreadLocal 默认情况下线程是得不到父线程的数据的【解决⽅案:InheritableThreadLocal】
实现原理
Thread -> ThreadLocal -> ThreadLocalMap -> Entry -> key,value
ThreadLocal 哈希处理
ThreadLocalMap 使⽤的是开放地址法来解决哈希冲突的,不像 HashMap使⽤的是链表法来解决哈希冲突。
单例模式
- 将构造函数设置成私有
- 创建一个私有的对象
- 创建一个公共提供单例对象的方法
DCL双重效验锁
锁策略
乐观锁
它认为一般情况下不会发生并发冲突,所以只有在进行数据更新的时候,才会检测并发冲突
CAS
”比较并交换“,是乐观锁的一种机制
组成:
V:内存值
A:旧值
B:新值
机制:
V == A ?
true(没有并发冲突) -> V = B :
false(并发冲突) -> A = B , continue
如果内存中的值等于旧值,则没有并发冲突,直接将旧值替换为新值即可
如果内存中的值不等于旧值,则进行自旋,将自己的旧值修改为内存值,然后再次进行比较
ABA问题
解决ABA问题
增加版本号,每次修改之后更新版本号
悲观锁
它认为通常情况下会出现并发冲突,所以它一开始就会加锁
synchronized就是悲观锁
共享锁/非共享锁(独占锁)
共享锁:一把锁可以被多个程序拥有,这就叫共享锁
非共享锁:一把锁只能被一个线程拥有,这就叫非共享锁
共享锁有哪些?读写锁中的读锁
非共享锁有哪些?synchronized锁
读写锁
定义:就是将一把锁分为两个,一个用于读数据的锁(也叫做读锁),另一把锁叫作写锁。
特点:读锁是可以被多个线程同时拥有的,而写锁则只能被一个线程拥有
java中的读写锁:ReentrantReaderWriterLock(默认是非公平的,true-公平)
读写锁的优势:锁的粒度更加小,性能也更高
读写锁中的读锁和写锁是互斥的
公平锁和非公平锁
公平锁:锁的获取顺序必须和线程方法的先后顺序保持一致,就叫做公平锁
非公平锁:锁的获取顺序和线程获取锁的前后顺序无关,就叫做非公平锁(默认所策略)
非公平锁的优点:性能比较高
公平锁的优点:执行是有序的,所以结果也是可以预期的
自旋锁
通过死循环一直尝试获取锁
synchronized (轻量级锁) 是自旋锁
可重入锁
当一个线程获取到一个锁之后,可以重复的进入
代表:synchronized,Lock
JUC 包
- 可重入互斥锁 ReentrantLock
- 信号量 Semaphore -》 实现限流 acquire() 和 release()
- 计数器 CountDownLatch -》 保证一组线程同时完成某个任务 await() 和 countDown()
- 循环屏障 CyclicBarrier -》 可以多次进行计数
HashMap、ConcurrentHashMap 、Hashtable 区别
- HashMap 是非线程安全的容器,他在 JDK1.7 会造成死循环,JKD1.8 会造成数据覆盖;Hashtable 和 ConcurrentHashMap 都是线程安全的。
- Hashtable 实现线程安全的手段比较简单,它是再 put 方法上整体加了一把锁,使用 synchronized 修饰,因此性能不高,所以使用频率比较低;而 ConcurrentHashMap 是 HashMap 在多线程下的替代方案,它在 JDK 1.7 的时候使用的 Lock 加分段锁的方案来实现线程安全问题的保障的,而在 JKD 1.8 的时候使用了大量的 CAS、volatile 来实现线程的,并且在 JDK 1.8 的时候读取的时候不加锁(读取的数据可能不是最新的,因为读取和写入可以同时进行),只有在写的时候才加锁。
以上是关于复习多线程相关知识的主要内容,如果未能解决你的问题,请参考以下文章