Java内存模型
主内存与工作内存
主内存可以勉强对应堆中的对象实例数据部分,工作内存对应虚拟机栈中的部分区域。
硬件上讲,主内存是物理内存,工作内存优先储存在寄存器和高速缓存中,程序大部分是在工作内存上工作的。
内存间交互操作
先行并发原则,判断访问是否线程安全,在来到工作内存和回到主存的一次操作都会有个缓冲。
read: mem->work_mem
load: work_mem->local_backup
use: local_backup->exec
assign: exec->local_backup
store: local_backup->mem
write: mem->save_mem
volatile
首先它并不是线程安全的,修改时需要不依赖原值,volatile只是在一个线程改变它时,其他所有线程立即可见,可以用来做并发控制。
如通知其他进程停止工作。
而且它不会被线程内乱序执行,可通过-XX:+PrintAssembly看汇编
在JDK1.5以后才完全可以不被乱序执行,DCL单例模式得以实现。
public class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton.getInstance();
}
}
long和double的特殊规则
只需要保证半个的非原子协定,但是jvm一般还是会保证的
原子性,可见性,有序性
- 原子性
- read,load,store,assign,use,write等对于基本数据类型访问的原子操作,lock和unlock可以控制更大范围。
- 可见性
- 当一个线程修改了一个变量时,其他线程可以立刻获得这一修改
- volatile
- synchronized,unlock之前必须有store,和write操作
- final 一旦被构造且this指针没有被传递出去(this指针逃逸),其他线程立即可见
- 有序性,本线程内观察都是有序的,观察其他线程时是无序的
- 线程内表现为串行语义
- 指令重排,工作内存和主内存的延迟
Java与线程
线程的实现
Java中的Thread的关键方法大部是Native的,所以与线程的操作大多与平台相关。
使用内核线程实现
程序一般使用LWP轻量级进程,每个LWP由KLP内核线程来支持
LWP和KLP是1:1关系
局限性:创建析构同步需要频繁的进行系统调用;消耗内核资源
用户线程
UserThread (UT),内核不感知这些线程,快速消耗低,P:UT= 1:N
阻塞如何处理,多处理器将线程映射到其他处理器等,用户处理起来会非常复杂甚至不可能。基本弃用
用户线程和LWP混合
UT:LWP=N:M的关系
Java线程的实现
1.2以后直接映射到LWP中
Java线程调度
协同式线程调度,抢占式线程调度。前者由线程自己控制,事情干完通知下一个线程,现在已经很少用
后者就是由系统来控制了,虽然java有Thread.yield()但是只能让出,不能获取。可以同过指定线程优先级,让优先级高的容易被系统选中。但是平台线程优先级会高于Java线程优先级,所以不能说Java优先级就是万能的。
状态转换
围绕Running来描述
- 创建未启动的线程
New->start->Running
- 无限期等待:无参Object.wait()/Thread.join(),LockSupport.park()
Running->wait()->Waiting->notify/notifyAll->Running
- 有限期等待:有参Object.wait()/Thread.join(),Thread.sleep(),LockSupport.parkNanos()/parkUntil()
Running->sleep()->TimedWaiting->sleep()->Running
- 阻塞:等待进入同步区
Running->synchronized->Blocked->synchronized->Running
- 结束:
Running->run结束->Running