JMM&&volatile&&线程状态

很多计算都需要处理器与内存交互,如读取数据,由于计算机存储设备与处理器运算速度差了几个数量级,就产生了速度矛盾,引入高速缓存解决了速度矛盾,但这也引入了一个新的问题,缓存一致性,多处理器中,每个处理器都有自己的高速缓存,而他们又共享同一主内存,可能产生缓存数据不一致的情况。除了增加高速缓存,处理器还可能对输入代码进行乱序执行优化。处理器会在计算之后将乱序执行结果重组,保证结果与顺序执行结果一致。但不保证程序各个语句计算的先后顺序与输入代码一致。因此如果一个计算任务依赖另一个计算任务的中间结果。那么其顺序性并不能靠代码先后顺序来保障。我们称之为指令重排序。

JMM主要定义程序中各个变量的访问规则,即将变量存储到内存及从内存中取出变量的底层细节。JMM规定所有的变量都存储在主内存中,每条线程还有自己的工作内存,保存了被该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作都在工作内存中进行。线程间变量的值传递通过主内存来完成。(主内存对应java堆中对象实例部分,工作内存对应虚拟机栈中的部分,更低层次的说,主内存对应物理硬件的内存,工作内存会优先存储于寄存器和高速缓存中)

 

内存间交互通过八种操作,lock unlock read load use assign store write,同时还有很多对这八种操作的限制,加上volatile的一些特殊规定,就完全确定了java中内存访问操作在并发下的安全性。但这种定义十分繁琐,所有有一个等效判断原则----happens-before。

Volatile

volatile保证此变量对所有线程的可见性。但java运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的。比如多个线程执行volatile变量的自加操作(a++),自加操作不为原子操作,所以不安全。所以如下两个情况,依旧需要加锁来保证原子性。

1.运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。

2.变量的值不受其他的变量的值的限制。如Range类的lower和upper边界必须遵循lower<=upper。

volatile第二个语义是禁止指令重排序优化,指令重排序在线程内表现为串行语义,但多线程情况下就会出现并发问题。通过volatile修饰,插入了一个内存屏障,使得指令重排序时不能将屏障后面的指令重排序到内存屏障前。

指令重排并不是任意重排,需要考虑数据依赖性,a+10的指令1和a*2的指令2以及tag=true的指令3中,指令1和指令2不能重排,而指令3可以重排至任意位置,这在本CPU中,重排序后看起来依旧是有序的,再通过内存屏障保障这三个指令都优先于屏障前执行。这样在其他线程中便能正确的获取内存屏障前的所有内容。

long和double具有费原子性协定,因为他们是64位数据,读写操作会被划分为2个32位操作来进行。所以多线程共享volatile修饰的long和double不是安全的。

JMM围绕3个特性来建立的

原子性,可见性和有序性

有序性:线程内表现为串行的语义,多线程角度所有的操作都是无序的。

 

 

happens-before

如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间存在happens-before关系。这里的两个操作可以是一个线程内的,也可以是在不同线程之间的。

//线程A
i=1
//线程B
j=i
//线程C
i=2

A先行发生于B,不考虑C,那么j肯定为1,但考虑C后,依然保持A先行发生于B,但C与B不存在先行发生原则,那么j的值不确定了,这时候不具备多线程安全性。

JMM中天然存在的一些先行发生关系有如下几个,如果两个操作之间的关系不是以下几个,且无法从以下规则推导出来,他们就没有顺序性保障,虚拟机就可以对他们随意的进行重排序。

程序次序规则:一个线程内,按照代码控制流顺序,前面的操作先行发生于后面的操作。

管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。

线程启动终止规则:thread对象的start方法先行发生于这个线程的每一个动作。终止检测后发生于线程的每一个动作。

线程中断规则:对线程interrupt方法的调用先行发生于被中断线程的代码检测到中断事件的发生。

对象终结规则:一个对象的初始化完成先行于finalize方法的开始。

volatile变量规则:对一个volatile域的写,先行发生于任意后续对这个volatile域的读。

传递性:A先行于B,B先行于C,则A先行于C。

简单例子:一个对象的成员值得getter和setter方法,在A线程调用Getter,B调用Setter,不存在以上所有规则,所以不是线程安全的,只有通过给这个对象的这个成员值加上volatile来赋予它volatile变量规则实现先行发生属性或是给Getter/Setter方法加上synchronized来赋予它先行发生属性,他才会编程多线程安全的。

线程状态

新建:创建后尚未启动。

就绪:线程启动后进入就绪状态。等待获取CPU执行权后进入运行状态。

运行:线程正在执行。

阻塞:线程运行被系统强行中断后进入阻塞状态,在恢复阻塞状态后只会进入就绪状态而不是直接进入运行状态。

  • 线程调用sleep()方法或者是yield()方法主动放弃所占用的处理器资源;
  • 线程调用了一个阻塞式的IO方法,在该方法返回之前,该线程被阻塞;
  • 线程试图获取一个被其它线程持有的锁;
  • 线程在等待某个通知;
  • 程序调用了线程的suspend()方法将线程挂起;

结束:已终止线程的状态。

  • run()或者call()方法执行完成线程正常结束;
  • 线程抛出一个未捕获的异常;
  • 直接调用线程的stop()方法结束线程;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值