《java并发编程实战》读书笔记2

3.共享变量
    编写正确的并发程序的关键在于对共享的、可变的状态进行访问管理。
3.1可见性
    可见性是微妙的,这是因为可能发生错误的事情总是与直觉大相庭径。在一个单线程化的环境里,如果想一个变量先写入值,然后在没有写干涉的情况下读取这个变量,你希望能得到相同的
返回值。这看起来是很自然的。但是当读和写发生在不同的线程中时,情况却根本不是这样。为了确保跨线程写入的内存可见性,你必须使用同步机制。
    在没有同步的情况下,编译器,处理器,运行时安排操作的执行顺序可能完全出人意料。在没有进行适当同步的多线程程序中,尝试推断那些"必然"发生在内存中的动作时,你总是会判断错误。
有一个简单的办法来避免这些复杂的问题:只要数据需要被跨线程共享,就进行恰当的同步。
3.1.1过期数据
    过期既不会发生在全部变量上,也不会完全不出现:一个线程可能会得到一个变量最新的值,但是也可能得到另一个变量先前写入的过期值。
3.1.2非原子的64位操作
    当一个线程在没有同步的情况下读取变量,它可能会得到一个过期值。但是至少它是可以看到某个线程在那里设定的一个真实数值,而不是一个凭空而来的值。这样的安全保证被称为是最低
险的安全性。最低限的安全性应用于所有的变量,除了一个例外:没有声明为volatile的64位数值变量(double和long)。java存储模型要求获取和存储操作都为原子的,但是对于非volatile的long和double
变量,JVM允许将64位的读或写划分为两个32位的操作。如果读和写发生在不同的线程,这种情况读取一个非volatile类型long就可能会出现得到一个值的高32位和另一个值的低32位。因此,
即使你并不关心过期数据,但仅仅在多线程中使用共享的、可变的long和double变量也可能是不安全的,除非将他们声明为volatile类型,或者用锁保护起来。
3.1.3 锁和可见性
    当访问一个共享的可变变量时,为什么要求所有线程由同一个锁进行同步:为了保证一个线程对数值进行的写入,其他线程也都可见。另一方面,如果一个线程在没有家当地使用锁的情况下
读取了变量,那么这个变量很可能是一个过期的数据。
    锁不仅仅是关于同步与互斥的,也是关于内存可见的。为了保证所有线程都能看到共享的、可变变量的最新值,读取和写入线程必须使用公共的锁进行同步。
3.1.4 Volatile
    同步的弱形式.通常被当作标识完成、中断、状态的标记使用。加锁可以保证可见性与原子性;volatile变量只能保证可见性。
    只有满足下面所有的标准后,才能使用volatile变量:
    1.写入变量时并不依赖变量的当前值;或者能够确保只有单一的线程修改变量的值。2.变量不需要与其他的状态变量共同参与不变约束。3.访问变量时没有其他的原因要加锁。
3.3.3ThreadLocal    
    ThreadLocal允许你将每个线程与持有数值的对象关联在一起。ThreadLocal提供了get与set访问器,为每个使用它的线程维护一份单独的拷贝。所以get总是返回由当前执行线程通过set设置
的最新值。
    ThreadLocal变量通常用于防止在基于可变的单体或全局变量的设计中出现(不正确)的共享。
3.4不可变性
    如果对象的状态不能被修改,这些风险与复杂度也就自然而然地消失了。
    创建后状态不能被修改的对象叫做不可变对象。不可变对象天生是线程安全的。他们的常量实在构造函数中创建的。既然它们的状态无法被修改,这些常量永远不会变。
    只有满足如下状态,一个对象才是不可变的:
    1.它的状态不能在创建后再被修改。
    2.所有域都是final类型并且它被正确创建(创建期间没有发生this引用的逸出)
3.4.1Final域
    final域使得确保初始化安全性称为可能,初始化安全性让不可变性对象不需要同步就能自由地被访问和共享。
    即便对象是可变的,将一些域声明为final类型仍然有助于简化对其状态的判断。
4
    设计线程安全类的过程应该包括下面3个基本要素:
    1.确定对象状态是由哪些变量构成的。
    2.确定限制窗台变量的不变约束。
    3.制定一个管理并发访问对象状态的策略。
    同步策略定义了对象如何协调对其状态的访问,并且不会违反它的不变约束或后验条件。它规定了如何把不可变性、线程限制和锁结合起来,从而维护线程的安全性,还指明了哪些锁保护哪些
变量。
4.1.1 收集同步需求
    维护类的线程安全性意味着要确保在并发访问的情况下,保护它的不变约束;这需要对其状态进行判断。对象与变量拥有一个状态空间:即它们可能处于的状态的范围。
状态空间越小,越容易判断它们。尽量使用final类型的域,就可以简化我们对对象的可能状态进行分析。
    很多类通过不可变约束来判定某一种状态是合法的还是非法的。
    不理解对象的不变约束和后验条件,你就不能保证线程安全性。要约束状态变量的有效值或者状态转换,就需要原子性与封装性。
几个概念:
原子操作:任务在执行过程中不能被打断的一系列操作
复合操作:任务在执行过程中可以被打断的一系列操作
不变约束:不变式表达了对状态的约束,这些状态是应该符合这个约束的值的组合。不变式可以代表某种业务规则。
先验条件:针对方法,规定了在条用方法之前必须为真的条件
后验条件:针对方法,规定了在条用方法之后必须为真的条件
可见性:  确保线程对变量的写入对其他线程是可见的。即刷新内存中的变量。

4.2实例限制
    把限制与各种适当的锁策略相结合,可以确保程序以线程安全的方式使用其他非线程安全对象。
    将数据封装在对象内部,把对数据的访问限制在对象的方法上,更易确保线程在访问数据时总能获得正确的锁。
    限制性使构造线程安全的类变得更容易。因为类的状态被限制后,分析它的线程安全性时,就不必检查完整的程序。
4.2.1 java监视器模式
遵循Java监视器模式的对象封装了所有的可变状态,并由对象自己的内部锁保护。    
    使用私有锁对象而不是对象的内部锁,有很多好处。私有的锁对象可以封装锁,这样客户代码无法得到它。
4.3.3 当委托无法胜任时
    如果类中还存在复合操作,单独的委托仍然不是线程安全的正确方法。在这种情况下,类必须提供它自有的锁以保证复合操作是原子的。除非整个复合操作也可以委托给一个状态变量。
    如果一个类由多个彼此独立的线程安全的状态变量组成,并且类的操作不包含任何无效状态转换时,可以将线程安全委托给这些状态变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值