JCIP_3_对象的共享_笔记总结

JCIP_3_对象的共享_笔记总结

多个线程访问共享的可变状态时需要进行正确的管理,可以通过同步来避免多个线程在同一时刻访问相同的数据。同步可以确保以原子的方式执行操作和内存可见性。当一个线程修改了对象状态后其它线程能够看到发生的变化。

如何安全地共享和发布对象使其能够被多个线程同时访问?

可见性

在没有同步的情况下我们无法确保执行读操作的线程能适时地看到其它线程写入的值,为了确保多个线程之间对内存写入操作的可见性必须使用同步机制。

在没有同步的情况下,编译器、处理理以及运行时等都可能对操作的执行顺序进行一些意想不到的调整。在缺乏足够同步的多线程程序中,要要对内存操作的执行顺序进行判断,几乎无法得出正确的结论。

失效数据的典型场景

在没有或缺乏正确同步的程序中,当某一线程查看某变量时,可能会得到一个已经失效的值,除非在每次访问变量时都使用同步,否则很可能获得该变量的一个失效值。失效值可能不会同时出现:一个线程可能获得某个变量的最新值,而获得另一个变量的失效值。

非原子的64位数值

64位操作系统下读写非volatile类型的64位数值变量是原子操作(区分具体处理器架构的支持),在32位操作系统上读写非volatile类型的64位数值变量会被分成两个32位的操作(一个操作取数值的前32位,另一个操作取数值的后32位)。

在多线程程序中使用共享且可变的longdouble等类型的变量时应使用volatile声明或使用锁保护起来。

加锁与可见性

同步可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果,实现同步的方式最简单的就是同步块、加锁。

这块有一个应用中很典型的点,在访问某个共享且可变的变量时要求所有线程在同一个锁上进行同步,在真正写并发程序时这块会由于各种原因导致出问题。

加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读或写操作的线程都必须在同一个锁上同步。

Volatile变量

volatileJava中提供的一种稍弱的同步机制。

volatile对变量可见性的影响比volatile变量本身更为重要。

volatile确保对变量的读操作总会返回最新写入的值。

读取volatile变量时不会产生加锁操作,因此不会使执行线程阻塞。

加锁机制即可以确保可见性又可以确保原子性,而volatile只能确保变量的可见性。

发布与逸出

发布,主要是指对象的引用被传递和保存到当前作用域之外。

逸出,是指当某个不应该发布的对象被发布了。

当发布一个对象时,该对象的非私有域中引用的所有对象同样会被发布。无论其它的线程会对已发布的引用执行何种操作,误用该引用的风险始终存在。当某个对象逸出后,你必须假设有个类或线程可能会误用该对象。

封装能够使得对程序的正确性进行分析变得可能,并使得无意中破坏设计约束条件变得更难。

典型的发布逸出场景

1.不该发布的对象被间接的发布共享。

2.对象还未真正构造完成就被共享。

资料

1.对象构造时this引用逸出比较详细的解释,http://stackoverflow.com/questions/3705425/java-reference-escape

线程封闭

访问共享的可变数据时需要使用同步,避免同步的最简单方式是仅在单线程内访问数据,这种技术就是线程封闭。

当某个对象封闭在一个线程中时,这种用法将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。

1.Swing中大量使用了线程封闭,Swing通过将组件或数据模型封装到Swing的事件分发线程中实现线程安全性。

2.JDBCConnection对象通过使用应用程序的连接池管理,这种连接管理模式在处理请求时隐含地将Connection对象封闭在线程中。

3种实现线程封闭的策略

1.Ad-hoc,通过程序实现来维护线程封闭特性,如手动维护一个类似ThreadLocal机制的全局变量使用volatile保证可见性的特性实现单写写多读;实现类似Swing事件分发线程机制避免多线程共享可变数据。Ad-hoc的实现和维护成本是非常高的并且它的确很脆弱。

2.栈封闭,这是线程封闭的一种特例,栈封闭只能通过局部变量才能访问对象,局部变量在执行线程的栈中,其它线程无法访问这个栈。栈封闭需要注意线程的栈空间是有限制的,注意不要使局部变量逸出。

3.ThreadLocal,这个类在通常的应用中使用的很频繁,ThreadLocal对象通常用于防止对可变的单例变量、全局变量进行共享。常见场景:

3.1.线程池中的Connection

3.2.事务支持,解决事务重入(嵌套)问题。

3.3.线程上下文级别的共享变量

应用系统线如果出现ThreadLocal频繁的使用说明应用系统的设计有问题,ThreadLocal很容易就被用滥了。

不变性

如果某对象在被创建后其状态不能被修改,则这个对象是不可变对象。不可变对象不存在中间状态,不可变对象本身就是线程安全的。

程序设计中一个最困难的地方就是判断复杂对象的可能状态,然而,判断不可变对象的状态却很简单。

对象不变性条件

1.对象创建后其状态不能被修改

2.对象的所有域都是final类型(由于对象private声明的域通过类的反射可以访问和修改所以也需要声明为final

3.对象是正确创建的(在对象创建期间没有产生逸出)

不可变对象与不可变对象引用

不可变对象与不可变对象的引用之间存在着差异,保存在不可变对象中的程序状态仍然可以更新。通过将一个保存新状态的实例来替换原有的不可变对象。

Final

Java内存模型中,final域有着特殊的语义,final域能够确保初始化过程的安全性,从面可以不受限制地访问不可变对象,并在共享这些对象时无须同步。

对于在访问和更新多个相关yojgjf出现的竞争条件问题,可以通过将这些变量全部保存在一个不可变对象中来消除。如果是一个不可变对象,那么当线程获得了对该对象的引用后,就不必担心另一个线程会修改对象的状态。如果要更新这些变量,那么可以创建一个新的容器对象,但其他使用原有对象的线程仍然会看到对象处于一致的状态。

p40示例

安全发布

对象的安全发布与对象的发布方式密切相关,有时看似没有问题的类定义因为该类的发布方式不同造成该类没有被安全的发布。

由于不可变对象是一种非常重要的对象,因为Java内存模型为不可变对象的共享提供了一种特殊的初始化安全性保证,这种保证还将延伸到被正确创建对象中所有final类型的域。

对象安全发布需要关注的几点

1.继承结构,在继承的类结构下,由于具体初始化逻辑很容易因为错误的继承特性而导致实例在初始过程中线程安全性已经被破坏。

2.对不可变对象的发布,由于Java线程模型保证了不可变对象的初始化过程,所以即使在发布这些对象时没有使用同布,对象的发布在多线程环境下也不会产生问题。

3.对可变对象的发布,可变对象在初始化可以产生代码重排等特征,这通常意味着线程在发布和使用该对象时都必须使用同步。

对象的实例化与对象的安全发布密切相关,考虑到一个对象未被正确的初始化就被安全发布的情况。

安全发布只能确保“发布当时”对象状态的可见性,相应的不可变对象因为发布后对象状态不会再被修改所以保证了线程安全。

安全发布的常用模式

安全发布的一个对象的约束条件就是对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:

1.在静态初始化函数中初始化一个对象引用

2.将对象的引用保存到volatile类型的域或者AtomicReferance对象中

3.将对象的引用保存到某个正确构造对象的final类型域中

4.将对象的引用保存到一个由锁保护的域中

5.通过使用线程安全的容器类可以确保安全发布

事实不可变对象

对象发布后其状态不会再被修改,这类对象就是事实不可变对象。

如果对象在发布后不会被修改,那么对于其他在没有额外同步的情况下安全地访问这些对象的线程来说,是没有问题的。所有的安全发布机制都能确保,当对象的引用对所有访问该对象的线程可见时,对象发布时的状态对于所有线程也将是可见的,并且如果对象状态不会再改变,那么就足以确保对该对象任何访问都是安全的。

可变对象

可变对象不仅在发布对象时需要使用同步,而且在每次对象访问时同样需要使用同步来确保对象修改操作的可见性。

对象的发布需求取决于它的可变性:

1.不可变对象可以通过任意机制发布。

2.事实不可变对象必须通过安全方式发布。

3.可变对象必须通过安全方式发布,并且该对象必须是线程安全或由某个锁保护起来。

安全地共享对象的一些策略

  1. 线程封闭
  2. 只读共享
  3. 线程安全共享,线程安全的对象在其内部实现同步,线程通过对象的公有接口进行访问。
  4. 保护对象,被保护的对象只能通过持有特定的锁来访问。

资源

1.http://jcip.net.s3-website-us-east-1.amazonaws.com/listings.html

2.Java并发编程实践

总结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值