进程, 线程, 协程
在java中没有协程, 其并发模型主要基于线程
线程和进程
- 进程: 系统运行的基本单位, 独立占用输入输出, 内部包含多个线程, 一个进程对应一个软件, 开销大
- 线程: 进程内部的并发执行, 更小粒度, 独立运行的最小单位
一个进程内可以存在多个并发线程
- 在你任务管理器里面看到的就是进程, 一般一个应用程序对应一个进程
- java的Main函数就是开启一个进程, 而thread是开启子线程
- java的并发是基于子线程实现的, 而不是多个Main函数
在高并发项目中, 依旧只有一个Main, 而是通过多个thread启动了多个子线程来实现并发执行
在分布式项目中, 存在多个Main, 在不同硬件软件服务器上实现不同功能, 经常与微服务相联合 [ 一般微服务就是分布式项目 ]
线程与进程数据结构
一个进程中可以有多个线程,多个线程共享进程的堆和方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。
并发不是并行
- 并发:两个及两个以上的作业在同一 时间段 内执行。
- 并行:两个及两个以上的作业在同一 时刻 执行。
实际上并发是同时间段执行, 并不是完全的同时执行
同步与异步区别
- 同步:发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待。
- 异步:调用在发出之后,不用等待返回结果,该调用直接返回。
需要进行并发, 一般就是通过异步调用开启多个线程
java线程和计算机组成原理中的线程关系
java线程本质是操作系统的线程, [ 操作系统线程不会让计算机CPU转换到管态, 而是要操作系统管理和控制 ]
- 用户线程:由用户空间程序管理和调度的线程,运行在用户空间(专门给应用程序使用)。
- 内核线程:由操作系统内核管理和调度的线程,运行在内核空间(只有内核程序可以访问)。
然后java线程算内核线程
保证并发安全三大特征
原子性, 可见性, 有序性
线程上下文不仅仅是上下文
线程上下文切换是一个线程被占用时, CPU切换到另外一个线程
线程池参数
- 核心线程数:线程池中的基本线程数量
- 最大线程数:当阻塞队列满了之后,逐一启动
- 最大线程的存活时间:当阻塞队列的任务执行完后,最大线长的回收时间
- 最大线程的存活时间单位
- 阻塞队列:当核心线程满后,后面来的任务都进入阻塞队列
- 线程工厂:用于生产线程
- 任务拒绝策略:阻塞队列满后,拒绝任务,有四种策略:
- (1)抛异常
- (2)丢弃任务不抛异常
- (3)打回任务
- (4)尝试与最老的线程竞争
死锁的条件
- 循环等待: 线程对资源的等待可以形成一个环
- 互斥: 线程之间是互斥的, 一个执行另外一个不能执行
- 不可抢占: 线程的资源一旦分配不能中断
- 请求保持: 线程一直请求资源, 未获取到前不会停止
针对死锁的预防就是从这四个方面下手, 破坏其中一个条件即可
- 破坏请求与保持条件:一次性申请所有的资源。
- 破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
线程的暂停
sleep和wait两个方法都可以暂停线程, 但是其使用情况和作用不同
sleep方法使得进程进入阻塞, 释放CPU资源, 但是其线程占用的其他资源不会释放[ 不释放锁 ]
用于当前线程暂停让其他进程执行
wait是让线程进入等待状态, 会释放CPU和其他占用资源[ 释放锁 ]
让其他线程使用资源
线程开启方式
- 使用thread
- 实现runnable接口
- 实现 Callable 接口:带有返回值
- 线程池创建线程
CAS
是什么
不是关键字, 而是一个算法思想
包含三个操作数——内存位置(V)、期望的原值(A)和新值(B)。如果内存位置V的值与期望的原值A相等,那么处理器会自动将该位置值更新为新值B。否则,处理器不做任何操作。无论哪种情况,它都必须在CAS指令结束之前返回该位置的值。
一种乐观锁机制, 但是不涉及线程的调用和顺序
当多个线程执行时, 会按照线程执行的快慢 "抢" 执行线程, 然后更新
同一时间对同一个数据只能被一个线程抢到更新, 其他线程全失败, 然后重新尝试
存在ABA问题, 就是一个变量在初次读取和准备赋值时读取结果相同, 但是可能被进行了两次修改
即 读取时为A 之后改为了B 又改回了A, 此时赋值可以执行, 进行的另外操作没有记录
解决方法是加版本号与时间戳
Synchrpnized关键字 加锁
是什么
来实现锁的功能的,它可以确保同一时间只有一个线程可以执行特定的代码块或方法。
使用
- 修饰方法 [ 锁对象实例 ]
- 修饰静态方法 [ 锁类 ]
- 修饰代码块 [ 锁指定对象 ]
通过对子线程使用的方法, 类加SynchronizedUsed, 实现线程安全
就是防止两个子线程同时刻执行一个方法导致数据混乱
作用: 在同步线程中, 防止同时执行
用该关键字修饰的方法, 对象, 在同一时刻只能有一个线程进入那个方法, 一个线程访问对象
注意项
- 构造方法不能修饰
- 底层涉及对象监视器
底层原理
Synchrpnized是基于对象监视器, 实际上是基于操作系统的并发控制
在使用时需要从用户态切换到核心态[ 目态切换到管态 ]
成本很高
优化
在jdk1.6后, 对其进行了优化
- 偏向锁:当一段代码没有别的线程访问,此时线程去访问会直接获取偏向锁
- 轻量级锁:当锁是偏向锁时,有另外一个线程来访问,会升级为轻量级锁。线程会通过CAS方式获取锁,不会阻塞,提高性能,
- 重量级锁:轻量级锁自旋一段时间后线程还没有获取到锁,会升级为重量级锁,重量级锁时,来竞争锁的所有线程都会阻塞,性能降低
注意,锁只能升级不能降级
Threadlocal
是线程的变量管理
类似vue里面的VUEX
VueX是全局共享, threadlocal是局部独立
实现线程之间数据的隔离, 减少开销, 增加内聚
原理
原理是为每个线程创建变量副本,不同线程之间不可见,保证线程安全。每个线程内部都维护了一个Map,key为threadLocal实例,value为要保存的副本。
可能内存泄漏
如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。
因为threadlocal的数据结构问题 [ threadlocalMap ] , 在垃圾回收时只会回收掉 threadlocal的 key, 而value会保存
解决方法是显示调用 remove方法来移除关联 threadlocal变量
原子类
在java中, 原子类变量是一类具有原子性操作的变量,这意味着原子变量的操作不会被线程调度机制打断,一旦开始,就会一直运行到结束,中间不会切换到任何别的进程。[ 类似的原子不可切分 ]
有基础数据类型的原子类, 数组类型, 引用类型
作用
保证线程安全, 效率高
原子类可以保证同一时间只会被一个线程修改
在性能上存在优势
可以替代在子线程中手动添加锁的操作