笔记_并发编程实践_十六

java存储模型
1.什么是存储模型:
(1)存储模型要回答这样一个问题:当一个线程为某变量赋值,什么时候其他线程可以看到这个值?

(2)平台的存储模型
<1>每一个处理器都有自己的缓存,并周期性地保持与主内存一直,助力器架构提供了不同级别的缓存一致性,几乎在任何时间内都允许处理器在相同的存储位置上看到不同的值。
<2>一种架构的存储模型告诉了应用程序可以从它的系统中获得何种担保,同时详细定义了一些特殊的指令,称为存储关卡或栅栏,用于在需要共享数据时得到额外的存储协调保证
<3>java为了屏蔽跨架构的存储模型之间的不同,java提供了自己的存储模型,jvm会在通过适当的位置上插入存储关卡。
<4>现代处理器不在提供顺序化一致性的模型,这个经典模型仅仅是现代多处理器的近似而已。
(3)重排序
<1>jmm允许从不同的角度观看要一个动作,使得该动作会以不同的次序执行,没有适当的同步的话,线程间的动作执行的顺序将被打乱,程序的行为变得不可预期。
(4)java存储模型的简介
<1>java存储模型的定义是通过动作的形式进行描述的,所谓的动作,包括变量的读和写、监视器加锁和释放锁,线程的启动和拼接。

<2>jmm为所有的程序内部的动作定义了一个偏序关系,叫作hapepens-before,要想保证执行动作b的线程看到动作a的结果(无论a和b是否发生在同一个线程中),a和b之间必须满足happens-before关系

<3>happensbefore关的法则包括:
《1》程序次序法则:线程中的每个动作A都happens-before于该线程中的每一个动作B,其中,在程序中,所有动作B都出现在动作a之后
《2》监视器法则:对于监视器锁的加锁happens-before与每一个后续对同一监视器的加锁(保证了synchronized代码对变量的修改动作对其他线程可见?)
《3》volatile变量法则:对volatile域的写入操作happens-before于每个后续对同一域的读操作(volatil保证的可见性)
《4》线程启动法则:在一个线程里,对Thread.start的调用会happens-before于每一个启动线程中的动作
《5》线程终结法则:线程中的任何动作都happens-before与其他线程检测到这个线程已经终结、或者从Thread.join调用中成功返回,或者Thread.isAlive返回false
《6》中断法则:一个线程调用另外一个线程的interrupt happens-before于被中断的线程发现中断(通过抛出InterruptedException或者调用isInterrupted和interrupted)
《7》终结法则:一个对象的构造函数结束happens-before于这个对象的finalizer的开始
《8》传递性:如果a happens-before 与b,且bhappens-before 于c,则ahappens-before与c
<4>动作仅仅需要满足偏序关系,但是同步动作——锁的获取与释放,以及volatile变量的读取与写入——确实满足全序关系(当偏序集中的任意两个元素都可比时,称该偏序集满足全序关系)
(5)“驾驭”在同步之上
<1>模拟Future的例子:由于volatile变量的happens-before法则保证对该修饰下的变量的写入在发生前于其读取,在方法适当位置放入写入代码,在另一方法的适当位置放入读取的代码使得两个方法的执行顺序被排序。(这中由volatile变量来创建排序,而不是真正使用volatile来发布该实际对象。happens-before排序是依赖其他原因创建的,而不是的专门创建用来发布X的 ,这种技术称为“驾驭(同步)”)
<2>这种“驾驭”技术相当容易出错,不能武断使用
<3>由类库担保的其他happens-before排序
《1》将一个条目置入线程安全容器happens-before于另一个线程从容器中获取条目(如BlockingQueue保证置入队列操作Happens-before获取操作。)
《2》执行CountDownLatch中的倒计时happens-before线程从闭锁的await中返回
《3》释放一个许可给Semphore happens-before 于从同一线程获得一个许可
《4》Future 笔仙的任务所发生的动作happens-before与另一个线程成功地从Future.get返回
《5》向Executor提交一个Runnable或Callable happens-before于开始执行任务
《6》最后,一个线程到达CyclicBarrier 或 Exchanger happens-before 于相同关卡(Barrier)或Exchanger点中的其他线程释放。如果CyclicBarrier使用一个关卡动作,到达关卡happens-before于关卡动作,依照次序,关卡动作happens-before于线程从关卡中释放
2.发布( 所谓发布对象是指使一个对象能够被当前范围之外的代码所使用)
(1)为不正确发布带来风险的真正原因在于“发布共享对象”与从“另一个进程访问“之间,缺少happens-before”排序

(2)除了不可变对象外,使用被另一个线程初始化的对象,是不安全的,除非对象的发布时happens-before于对象的消费线程使用它

(3)安全发布的几种方法:使用volatile修饰的变量可以保证happens-before,使用锁,使用BlockingQueue,放入后再被其他线程取出

(4)安全初始化的技巧:
<1>主动初始化:即在使用静态变量初始化对象,这种对象将由jvm保证其安全发布,缺点是后续修改仍然有肯能使得数据变“脏”(这项技术可以和jvm的惰性类加载相结合,创建一种惰性初始化技术)
<2>双检查锁
《1》双检查锁(double-checked locking)的问题:由于构建对象前,对象的引用已经构建,所以其他线程在查看该引用时可能出现看到部分创建的对象(把该对象生命为volatile的话可以保证该对象是完整构建的(确保对象值得写入happens-before,该对象的读取),但是由于这样的优化效果越来越不明显,现在已经被废弃)(可使用上面惰性初始化技术代替,但是要注意类的懒加载机制如何结合)

3.初始化安全性
(1)保证初始化的安全性就可以让正确创建的不可变对象在没有同步的情况下,可以被安全地跨线程共享。(而不管他们是如何发布的,即使发布时也存在竞争)
因此Java内存模型为不可变对象的共享提供了一种特殊的初始化安全性保证
(2)初始化安全性可以保证,对于正确创建的对象,无论它是如何发布的,所有线程都将看到构造函数设置的final域的值,任何可以通过final域接触到的变量(final域的数组和hashMap等里面的内容),都可以保证对其他线程时可见的。(原因是含有final域的对象可以抑制重排序,所有构造函中要写入值得final域,以及任何通过这些值可以到达的变量都会在构造函数完成后被冻结,而且可以保证任何获得该引用的线程都至少可以看到冻结值一样的新值,用于向final域可以到达的初始变量写入值得操作并不会与构造后的操作一起被重排序)(不可变对象的由来?)
(3)初始化安全性的保证只有通过final域触及的值,在构造函数完成时才是可见的,对于通过非final域触及的值,或者创建完成后可能改变的值必须使用同步来确保可见性(也就是即使是final德尔数组或map,如果其中的元素时在构造函数意外被改变的,一样需要同步)

只要对象是正确构造的(意即不会在构造函数完成之前发布对这个对象的引用),然后所有线程都会看到在构造函数中设置的 final 字段的值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值