JAVA并发三个特性
并发编程Bug根源:原子性、可见性、有序性
原子性
一个或者多个操作,要么全部执行且不受其他因素影响,要么全不执行。
如 i++ 操作在并发情况下就会出现计算错误情况
如何解决原子性问题
使用synchronized关键字
使用Lock锁保证原子性
使用CAS保证原子性
可见性
一个或者多个线程访问同一个变量时,一个线程对变量的修改结果,其他线程是立刻看到修改后的值。
如果解决可见性问题
使用volatile关键字
使用synchronized关键字
使用内存屏障
使用Lock锁保证可见性
java可见性底层2种实现
1.使用内存屏障(volatile synchronized Thread.sleep(10))
lock addl $0x0,(%rsp)
2.cpu上下文切换(Thread.sleep(0) Thread.yield())
有序性
程序执行的顺序按照代码顺序执行。
为什么会产生有序性问题? 编译器和处理器会对指令进行重排序(提高性能),所以产生有序性问题。
如何解决有序性
使用内存屏障保证有序性
使用volatile关键字
使用synchronized关键字
使用Lock锁保证有序性
JMM模型
在并发编程中,存在两个关键问题
1.如果保证线程间通信(数据交换)
2.多线程间如何同步(线程执行顺序性)
java中采用JMM模型
主存和线程内存数据交互流程
锁的内存语义
当线程获取锁时,线程会把当前线程本地内存置位无效
当线程释放锁时,会把线程内存中的变量更新到主存中
volatile的内存语义
当volatile读时,JMM会把当前线程的内存置位无效,从主存中重新读取变量
当voaltile写时,JMM会把当前线程内存中变量刷新到主存中
volatile禁止重排序
当使用volatile时,会插入内存屏障,禁止编译器进行重排序
使用lock前缀,实现内存屏障效果
happens-before
JSR-133使用happens-before的概念来指定两个操作之间的执行顺序。JMM可以通过happens-before关系向程序员提供跨线程的内存可见性保证。
JSR-133规范对happens-before关系的定义如下:
1)如果一个操作happens-before 另一个操作,那么第一个操作的执行结果将对第二个操作可见,而 且第一个操作的执行顺序排在第二个操作之前。 这是JMM对程序员的承诺, 注意,这只是JMM向程序员做出的保证。
2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果 一致,那么这种排序并不非法,也就是说,JMM允许这种排序。
这是JMM对编译器和处理器重排序的 约束原则 JMM遵循一个基本原则:只要不改变程序的执行结果,编译器和处理器怎么优化都行。
as-if-serial语义保证单线程内程序的执行结果不被改变
happens-before关系保证正确同步的多线程程序的执行结果不被改变。