参考及引用
线程也被称之为一个轻量级进程, 是基本的调度单位.
线程允许在同一个进程中同时存在多个程序控制流.
线程会共享进程范围内的资源, 例如内存句柄.
但每个线程都有各自的线程计数器,栈,以及局部变量等.
由于同一个进程中的所有线程都将共享进程的内存地址空间.
因此这些线程都能访问相同的变量,并在同一个堆上分配对象.
因此需要一种比在进程间共享数据粒度更细致的数据共享机制.
当多个线程访问某个类时, 这个类始终都能表现出正确的行为, 则称这个类是线程安全的. 在线程安全性的定义中,最核心的概念就是正确性.即, 某个类的行为与其规范完全一致.
wiki:
The Java memory model describes how threads in the Java programming language interact through memory. Together with the description of single-threaded execution of code, the memory model provides the semantics of the Java programming language.
Java虚拟机规范中,试图定义一种java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异.其主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节.[JVM P362]
ref: 图 线程/主内存/工作内存三者关系.
JMM中,定义了8种操作来完成: lock,unlock,read,load,use,assign,store,write. JMM是围绕着在并发过程中,如何处理原子性,可见性和有序性这3个特征来建立的.
JMM为程序中的所有操作定义了一个偏序关系[1].称之为happens-before(先行发生)关系.
要想保证执行操作B的线程看到操作A的结果,(无论AB是否在同一个线程).那么AB之间则需要必须满足Happens-Before关系.若AB之间不存在Happens-before关系,则JVM可以对它们任意的重排列.
public class A {
private int value = 0;
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
判断上例是否线程安全?
当且仅当满足以下所有条件时,才应该使用volatile变量:
1. 对变量的写入不依赖变量的当前值, 或者确保只有单个线程更新变量的值.
2. 该变量不会与其他状态变量一起纳入不变性条件中.
3. 在访问变量时不需要加锁.
分类 | 现象名称 | 常见原因 | 处理方案 |
---|---|---|---|
活跃性问题 | 死锁 | 每个线程都拥有其他线程需要的资源, 同时又等待其他人已经拥有的资源,且每个人在获取到完整所需资源前都不会放弃已有资源. |
|
饥饿 | 线程由于无法访问它所需的资源而不能继续执行.常见资源: CPU时钟周期. 如: 对线程优先级使用不当, 或持有锁的线程执行一些无法结束的结构. |
注意线程优先级与平台相关. 注意线程执行结构 |
|
响应性问题 | 等待导致相应不及时. | 视场景而定 | |
活锁 | 线程不会阻塞,但也不能继续执行.多个相互协作的线程都对彼此进行响应从而修改各自状态,使得任何一个线程都无法继续执行. | 在重试机制中引入随机机制 |
分类 | 现象名称 | 现象描述 | 相关 |
---|---|---|---|
性能和可伸缩性 | 性能和可伸缩性 | 上下文切换: 如果可运行的线程大于CPU的数量,那么操作系统最终会将某个正在运行的线程调度出来,从而使其他线程可以使用CPU.这一过程将导致一次上下文切换. | 它将保存当前运行线程的执行上下文,并将新调度进来的线程的执行上下文设置为当前上下文. |
内存同步 | 多线程下,需对共享变量进行同步.
|
无须关注无竞争同步带来的开销. 同步会增加共享内存总线的通讯量, 而总线的带宽是有限的. |
|
阻塞 | 当锁上发生竞争时,竞争失败的线程会阻塞. | 自旋锁/通过操作系统挂起. 效率的高低取决于上下文切换的开销,以及等待时间. |
减少锁的竞争能够提高性能和可伸缩性.
有三种方式可以降低锁的竞争程度:
分配 | 优化方式 | 典型应用 |
---|---|---|
优化 |
减少所的持有时间 | 缩小锁范围 减小锁粒度 |
降低锁的请求频率 | 锁分解 锁分段 |
|
使用带有协调机制的锁,这些机制允许更高的并发机制 | 放弃使用独占锁, 替换为并发容器,读写锁, 不可变对象和原子变量 |
锁分段实例: concurrentHashMap
元素: 多个线程, 共享的资源/对象
规则: 对于每个线程而言,在等待条件为真(获取对象锁/资源,信号量为真)前,一直阻塞,条件为真时继续执行.
条件队列(condition queue): 它使得一组线程,能够通过某种方式来等待特定的条件变为真. 条件谓词(condition predication):使某个操作成为状态依赖操作的前提条件.
e.g: 对于有界队列中,当队列不为空,take方法才能执行,否则必须等待.则,对于take方法来说,"队列不为空"就是它的条件谓次.
在条件等待中, 存在一种三元关系: 加锁, wait, 一个条件谓词.
//画图:P245
将条件队列封装起来,这种建议与线程安全最常见的设计模式并不一致:该模式建议使用对象的内置锁来保护对象自身的状态.在该情况下,对象自身既是锁,又是条件队列.
abstract queued synchronizer AQS是一个用于构建锁和同步器的框架.解决了实现同步器时设计的大量细节问题, 如解决了等待线程采用FIFO队列操作顺序. JUC中的许多可阻塞类,如ReentrantLock, ReentrantReadWriteLock等,都是基于AQS构建的.
各种形式的获取和释放操作.
boolean acquire() throws InterruptedException {
while(当前操作不允许获取操作){
if(需要阻塞或许请求){
如果当前线程不再阻塞队列中,则将其插入队列中
阻塞当前线程
} else{
return false;
}
}
可能更新同步器状态;
如果线程位于队列中,则移除
return ture;
}
void release(){
更新同步器的状态;
if(新的状态允许某个被阻塞的线程获取成功){
接触队列中一个或多个线程的阻塞状态
}
}
管理同步器中的状态.
更新同步器中的状态.