《深入理解java虚拟机》笔记:java内存模型JMM,volatile,java与线程


半个读书笔记,没什么含量。


JMM:

JMM围绕在并发过程中如何处理原子性,可见性和有序性三个特征来建立。
java内存模型JMM与实际硬件模型有比较类似的地方:

内存 => 高速缓存 => cpu
主内存 => 工作内存 => 线程

由于I/O速度与cpu速度差距过大,所以在二者之间加入一层与cpu速度相对接近的高速缓存。cpu将数据从内存读入高速缓存中,计算过后将数据再写入缓存,最后再重新写进内存。由此解决了速度上的矛盾,也带来了缓存一致性的问题。

JMM的模型与之类似,每个java线程都有自己的工作内存,线程私有,其他线程是访问不到的。java线程将变量在主内存中的副本拷贝到工作内存中,线程对变量的操作都在工作内存中进行。

在JMM模型中,主内存与工作内存之间有8种原子性交互操作:

lock(锁定):一个线程独占主内存的一个变量
unlock(解锁):将线程锁定的主内存变量释放出来
read(读取):将变量从主内存 -> 到工作内存中
load(加载):将变量值从工作内存 -> 放入工作副本
use(使用):工作内存变量 -> 放入执行引擎中
assign(赋值):从之型引擎接收到新值 -> 放入工作内存变量副本
store(存储):变量 工作内存 -> 放回主内存中
write(写入):将新值放回主内存变量中

JMM规定这8种操作需要满足一定的规则,简而言之为“先行发生原则”,如果不满足这个规则,即为线程不安全的。
需要注意“先行发生”并不代表“时间上的先发生”,因为有指令重排序等原因的存在

VOLATILE:

volatile变量可以保证可见性:

JMM规定,store和write要同时出现,不能单独出现其中一种操作。而volatile关键字则规定assign和store操作必须要连起来出现。所以意味着一旦变量值被修改,则会被强制写回到主内存当中。
JMM还规定,read和load操作要成对出现,而volatile关键字规定load与use操作必须要连起来出现,这样就保证了线程每次都要从主内存中读取最新的变量值进来。
通过以上的一些规定,volatile保证了可见性,即变量被某线程修改后,其他线程就会看见。

volatile不保证原子性,所以依然会有线程安全问题

典型例子:count++问题

public class FakeVolatile implements Runnable {
	
	static FakeVolatile fakeVolatile = new FakeVolatile();
	
	public static volatile int i = 0;

	public static void main(String[] args) throws InterruptedException {
		for(int j = 0 ; j < 20 ; j++) {
			new Thread(fakeVolatile).start();
		}
		while(Thread.activeCount() > 1) {
			
		}
//		Thread t1 = new Thread(fakeVolatile);
//		Thread t2 = new Thread(fakeVolatile);
//		Thread t3 = new Thread(fakeVolatile);
//		Thread t4 = new Thread(fakeVolatile);
//		Thread t5 = new Thread(fakeVolatile);
//		t1.start();
//		t2.start();
//		t3.start();
//		t4.start();
//		t5.start();
//		t1.join();
//		t2.join();
//		t3.join();
//		t4.join();
//		t5.join();
		System.out.println("i=" + i);
	}

	@Override
	public void run() {
		for(int x = 0 ; x < 10000 ; x++)
			i++;
	}

}

理论上的结果为200000,然而实际上什么结果都有,反正都比20万小。原因就是i++不是原子类操作,所以虽然可以保证volatile读到的变量是主内存最新的,但是在i++的三步操作中,可能就有别的线程已经更新值了,这样就出现了线程不安全的问题。

volatile还能禁止指令的重排序优化

作用:双重校验的单例模式,https://blog.csdn.net/qq_34785454/article/details/83049079
这篇文章里有提及,通过禁止指令重排序保证单例模式。

volatile编译后通过设置内存屏障来禁止重排序,后面的指令不能重排序到屏障之前的位置。

除了volatile,synchronized和final也能保证可见性。synchronized关键字规定,unlock解锁变量之前,必须先执行store和write操作;而lock上锁之前,则必须把线程工作内存中的副本清空。通过这样强制修改变量写入主内存,读取时也从主内存读取最新值。
final关键字变量在初始化完成后即可保证可见性。

线程的实现:

内核线程:直接由操作系统内核支持,程序中使用轻量级进程,与内核线程1对1关联。需要在用户态和内核态之间切换,消耗资源大,系统能支持的轻量级进程也有限,因为系统资源有限。
用户线程:建立在用户空间的线程库上,系统内核无法感知。基本不需要切换到内核态,速度快消耗低。劣势:没有内核支持,所有线程操作都要自己处理,现在基本不用了。
用户线程与轻量级进程混合实现:二者比例不固定,多对多。用户线程能处理的操作还在用户态,需要切换到内核态则使用轻量级进程。
java线程调度方式:1、协同式线程调度,线程通知系统进行切换,不怎么用了。2、抢占式线程调度,java使用的调度方式,系统自己决定执行的线程,可能参考线程优先级,优先级越高,越容易被系统选择执行。
java线程状态:
1、new
2、runnable:包括ready和running
3、waiting:等待被其他线程显式唤醒。wait()方法及join()方法等会进入这个状态。
4、Timed Waiting:一定时间后由系统自动唤醒。sleep()方法,带时间参数的wait,join方法等会进入这个状态。
5、blocked:线程等待锁时进入的状态
6、terminated:线程运行结束
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值