Effective Java 笔记(十二)

相关读书笔记列表

NO.48 对共享可变数据的同步访问
  同步,不仅可以阻止一个线程看到对象处于不一致的状态中,它还可以保证通过一系列看似顺序执行的状态转变序列,对象从一种一致的状态变迁到另一种一致的状态。
  synchronized关键字可以保证在同一时刻,只有一个线程在执行一条语句,或者一段代码块。java语言保证读或写一个变量是原子的,除非这个变量的类型是long或double.
  java的内存模型决定,为了在线程之间可靠地通信,以及为了互斥访问,对原子数据的读写进行同步是需要的。看一个可怕的例子:

  对其改进,只需要在generateSerialNumber的声明中增加synchronized修饰符即可。
  为了终止一个线程,一种推荐的做法是让线程轮询某个域,该域的值如果发生变化,就表明此线程就应该终止自己。下面的例子就是这个思路,但在同步出了问题。

  对其改进如下:

  另一种改进是,将stopRequested声明为volatile,则同步可以省略。
  再来看迟缓初始化(lazy initialization)问题,双重访问模式并不一定都能正常工作,除非被共享的变量包含一个原语值。看例子:

  最容易的修改是省去迟缓初始化:

  或者使用正确的同步方法,但可能增加少许的同步开销:

  按需初始化容器模式也不错,但是它只能用于静态域,不能用于实例域。

  简而言之,无论何时当多个线程共享可变数据的时候,每个读或写数据的线程必须获得一把锁。如果没有同步,则一个线程所做的修改就无法保证被另一个线程所观察到。


NO.51 不要依赖于线程调度器
  不能让应用程序的正确性依赖于线程调度器。否则,结果得到的应用程序既不健壮也不具有可移植性。作为一个推论,不要依赖Thread.yield或者线程优先级。这些设施都只是影响到调度器,它们可以被用来提高一个已经能够正常工作的系统的服务质量,但永远不应用来“修正”一个原本并不能工作的程序。
  编写健壮的、响应良好的、可移植的多线程应用程序的最好办法是,尽可能确保在任何给定时刻只有少量的可运行线程。这种办法采用的主要技术是,让每个线程做少量的工作,然后使用Object.Wait等待某个条件发生,或者使用Thread.sleep睡眠一段时间。


NO.52 线程安全性的文档化
  每个类都应该清楚地在文档中说明它的线程安全属性。在一个方法的声明中出现synchronized修饰符,这是一个实现细节,并不是导出的API文档的一部分。
  一个类为了可被多个线程安全地使用,必须在文档中清楚地说明它所支持的线程安全性级别。
    非可变性(immutable)-这个类的实例对于其它客户而言是不变的,不需要外部的同步。参见13条。
    线程程安全的(thread-safe)-这个类的实例是可变的,但是所有的地方都包含足够的同步手段,这些实例可以被并发使用无需外部同步。
    有条件的线程安全(conditionally thread-safe)-这个类(或关联的类)包含有某些方法,它们必须被顺序调用,而不能受到其它线程的干扰,除此之外,这种线程安全级别与上一种情形相同。为了消除被其他线程干扰的可能性,客户在执行此方法序列期间,必须获得一把适当的锁。如HashTable或Vector,它们的迭代器要求外部同步。如:

    线程兼容的(thread-compatible)-在每个方法调用的外围使用外部同步,此时这个类的实例可以被安全的并发使用。如ArrayList或HashMap
    线程对立的(thread-hostile)这个类不能安全地被多个线程并发使用,即使所有的方法调用都被外部同步包围。通常情况下,线程对立的根源在于,这个类的方法要修改静态数据,而这些静态数据可能会影响到其它的线程。
  对于有条件的线程安全类,在文档中指明“为了允许方法调用序列以原子方式执行,哪一个对象应被锁住”.


NO.53 避免使用线程组
  除了线程、锁和监视器之外,线程系统还提供了一个基本的抽象,即线程组(thread-group)。然而线程组并没有提供太多有用的功能。
  一个例外是,当线程组中的一个线程抛出一个未被捕获的异常时,ThreadGroup.uncaughtException方法会被自动调用。“执行环境”使用这个方法,以便用适当的方式来响应未被捕获的异常。


NO.54 保护性地编写readObject方法
  编写一个类的readObject方法,相当于编写一个公有的构造函数,无论给它传递一个什么样的字节流,它都必须产生一个有效的实例。下面是缩写健壮的readObject方法的指导原则:
①对于对象引用域必须保持为私有的类,对“将被保存到这些域中的对象”进行保护性拷贝。非可变类的可变组件就属于这一类别。
②对于具有约束条件的类,一定要检查约束条件是否满足,如果不满足的话,则抛出一个InvalidObjectException异常。这些检查应跟在所有的保护性拷贝之后。
③如果在对象图被反序列化之后,整个对象图必须都是有效的,则应该使用ObjectInputValidation接口。
④无论是直接方式还是间接方式,都不要调用类中可被改写的方法。
⑤readResolve方法有可能取被用来替代保护性的readObject方法。
  不严格地说,readObject是一个“用字节流作为唯一参数”的构造函数。当面对一个人工伪造的字节流的时候,readObject产生的对象会违反它所属的类的约束条件。初步的方法,是在readObject方法进行约束性检查,如下例:

  对上述的防范仍可进行攻击:伪造一个字节流,这个字节流以一个有效的Period实例所产生的字节流作为开始,然后附加上两个额外的引用,指向 Period实例中的两个内部私有Date域,攻击者通过引用攻击内部域。所以,当一个对象被反序列化的时候,对于客户不应该拥有的对象引用,如果哪个域包含了这样的对象引用,则必须要做保护性拷贝,这是非常重要的。如下例:


 
NO.57 必要时提供一个readResolve方法
  无论是 singleton,或是其他实例受控(instance-controlled)的类,必须使用readResolve方法来保护“实例-控制的约束 ”。从本质上来讲,readResovle方法把一个readObject方法从一个事实上的公有构造函数变成一个事实上的公有静态工厂。对于那些禁止包外继承的类而言,readResolve方法作为保护性的readObject方法的一种替代,也是非常有用的。
  如下sigleton类:

  如果Elvis实例序列化接口,则下面的readResolve方法足以保证它的sigleton属性。

  不仅仅对于singleton对象是必要的,readResolve方法对于所有其它的实例受控类(如类型安全枚举类型)也是必需的。
  readResolve方法的第二个用法是,就像在第56条建议的那样,作为保护性的readObject方法的一种保守的替代选择。此时,第56条中的readObject方法可以下例的例子替代:

  对于那些允许继承的类,readResolve方法可能无法替代保护性的readObject方法。如果超类的readResolve方法是final 的,则使得子类实例无法被正常地反序列化。如果超类的readResolve方法是可改写的,则恶意的子类可能会用一个方法改写它,该方法返回一个受损的实例。

 

      结束语:花了好几个月断断续续地把这本书看完了,期间做了好几个项目,忙了一段时间,看书的进度有些拖延,现在看之前整理的笔记都有点遗忘了,不过没关系,以后可以返回去看看,温故而知新嘛。。。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值