66:同步访问共享的可变数据

synchronized:1互斥,阻止线程看到的对象处于不一致的状态;2保证线程在进入同步区时能看到变量的被各个线程的所有修改

Java中,除了long或者double,“读”或者“写”一个变量是原子的。注意:是读或者写单个动作是源自的,而不是读写这两个动作整体是原子的。

由于虚拟机会对代码进行优化,所以可能会导致一些错误:可能你想的是在另一线程中改变done的值来终止while循环,但是优化之后却无法做到这样。要避免这样的优化错误,就必须对done同步。

//优化前,即程序员所写
while(!done){
++i;
}
//优化后 测试环境为win7的Eclipse不会进行此优化,在HopSpot Server VM中会进行此优化
if(!done){
wile(true){
++i;
}
}

volatile:可以保证线程之间的通信效果,但是无法保证互斥访问。即任何读线程读到的最近一次由任何线程修改后的值。但应该特别注意一些操作的原子性,比如i++,++操作会先读后写,即即使有volatile,也可能会在++操作读了值之后,+1操作之前而读取到值,即没有读到+1后的值。

67:避免过度同步

避免过度同步:为了避免死锁和数据破坏(一般由锁的可重入机制造成),千万不要在同步区域内部调用外来方法(即可能被覆盖的方法或者由客户端以函数对象提供的方法),应该尽量限制同步区域内部的工作量。外来方法的调用应放在同步区域之外,这叫做“开放调用”,可以避免死锁并极大提高并发性。

当设计一个类的时候,需要考虑是否应该在类的内部实现同步,使之线程安全,并能获得更高的并发性。在这个多核时代,这比不要过度同步更为重要。

过度同步的坏处并不是指获取锁所花费的CPU时间,而是:1失去了并行的机会,以及因为需要确保每个核都有一个一致的内存视图而导致的延迟;2会限制VM优化代码执行的能力

68:executor和task优先于线程

即优先使用Executor和task(即Runnable和Callable)而不是Thread,可以降低创建的线程个数,提高性能,而且可以获得更多的线程策略,极大降低编码难度。但应该认真选择合适的ExecutorService,比如一般的轻量程序选择Executors.newCachedThreadPool,但是对于高负载的服务器,由于缓存线程池可能会根据需求而不断增加新线程,可能导致CPU全部被占用,最终导致奔溃,这时就应该选择Executor.newFixedThreadPool以限制总线程数。

69:并发工具优于wait和notify

优先使用java.util.concurrent中的并发工具,因为使用notify-wait可能存在以下情况使等待线程在条件不满足的情况下苏醒过来:

  1. 另一个线程可能已经得到了锁,并且从一个线程调用notify的那一刻起,到等待线程苏醒过来的这段时间,得到锁的线程已经改变了受保护的状态
  2. 其他线程意外或者恶意调用notify
  3. 通知线程过度大方,使用notifyAll唤醒了某些并不满足条件的线程
  4. 在没有通知的情况下,等待线程也可能苏醒过来(很少)

如果需要使用notify-wait模式,则:

  1. 始终使用wait循环模式调用wait,而不应该在循环之外调用wait
  2. 从优化的角度看,如果所有的等待线程都在等待同一个条件,而每次只有一个线程可以从这个条件唤醒,那么应该使用notify。
  3. 优先使用notifyAll而不是notify,以保证程序的活性,虽然使用notifyAll会唤醒一些不相关线程,造成一定的性能问题,但是这会保证程序的正确性,而且可以避免恶意程序吞掉了某个notify。

70:线程安全性的文档化

每个类都应该有对线程安全的文档说明。线程安全分为五个级别:

  1. 不可变的:例如String,Long,BigInteger,不需要外部同步
  2. 无条件的线程安全:类内部已进行足够的同步,无需外部的同步。应该考虑使用使用私有锁对象来代替同步的方法,以防止客户端程序和子类的不同步干扰。
  3. 有条件的线程安全:部分方法需要外部同步
  4. 非线程安全:客户必须对每个调用都进行同步,例如通用集合,如ArrayList,HashMap
  5. 线程对立的:即使有外部同步,也不能安全的被并发使用

71:慎用延迟初始化

大部分情况应该正常的初始化,除非域只有在类的实例部分被访问,并且这个域的初始化成本很高,则可能值得延迟初始化。

 //静态域应该使用lazy initialization holder class模式
private static class FieldHolder{
static final FieldType field = computeFieldValue():
} static FieldType getField() {return FieldHolder.field;} //当它第一次被调用时,第一次读取field,导致FieldHolder类得到初始化 //对于实例域应该使用双重检查模式
private volatile FieldType field;
FieldType getField(){
FieldType result = filed; //result的作用:在field被初始化的情况下只读取field一次
if(result == null) { //假如没有result,读取field
synchronized(this) {
result = field;
if(result == null) { //可能在获得锁的期间,field被其他线程初始化了
field = result = computeFieldValue():
}
}
return result; //假如没有result,读取field
} //对于可以接受重复初始化的字段,即computeFieldValue():消耗不大的字段,则可以省去第二次检查,变成单重检查模式
private volatile FieldType field;
FieldType getField(){
FieldType result = filed;
if(result == null) {
field = result = computeFieldValue():
}
return result;
}

72:不要依赖于线程调度器

由于调度算法的区别,任何依赖线程调度器来达到正确性或者性能要求的程序,很有可能都是不可移植的。也不应该依赖Thread.yield(对于一般程序员来说,其唯一用途是在测试期间人为的增加程序并发性)以及线程优先级(线程优先级是Java平台上最不可移植的特性),线程优先级可以提高一个已经能正常工作的程序的服务质量,但永远不应该用来修正一个原本不能正常工作的程序

要编写健壮的、响应良好的、可移植的多线程程序,最好的办法是保证可运行的线程平均数量不明显多于处理器数量。而保证可运行线程数量尽可能少的方法是让每个线程做些有意义的工作。

73:避免使用线程组

线程组(ThreadGroup)存在很多缺陷,已经过时。如果需要处理线程的逻辑组,应该使用Executor

Effective Java 阅读笔记——并发的更多相关文章

  1. Effective Java阅读笔记——引言

    “我很希望10年前就拥有这本书.可能有人认为我不需要任何Java方面的书籍,但是我需要这本书.” ——Java之父 James Gosling 在图书馆找到这本java著作时,首先看到了这句话.   ...

  2. Effective Java 阅读笔记——枚举和注解

    30:用enum代替int常量 当需要一组固定常量的时候,应该使用enum代替int常量,除了对于手机登资源有限的设备应该酌情考虑enum的性能弱势之外. 31:用实例域代替序数 应该给enum添加i ...

  3. Effective Java 阅读笔记——方法

    38:检查参数的有效性 每当编写方法或者构造器的时候,应该考虑它的参数有哪些限制,在方法的开头处对参数进行检查,并且把这些限制写入文档. 注意: 对于公有方法,应该使用@throws标签在文档中说明违 ...

  4. 《Effective Java》笔记45-56:通用程序设计

    将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性. 要使用局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方才声明,不要过早的声明. 局部变量的作用域从它被声明的 ...

  5. Effective java读书笔记

    2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己 计在16年要看12本书,主要涉及java基础.Spring研究.java并发.J ...

  6. Effective Java读书笔记完结啦

    Effective Java是一本经典的书, 很实用的Java进阶读物, 提供了各个方面的best practices. 最近终于做完了Effective Java的读书笔记, 发布出来与大家共享. ...

  7. Effective Java 学习笔记之第七条——避免使用终结(finalizer)方法

    避免使用终结方法(finalizer) 终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的. 不要把finalizer当成C++中析构函数的对应物.java中,当对象不 ...

  8. Effective Java 读书笔记(一):使用静态工厂方法代替构造器

    这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文&l ...

  9. Effective Java 读书笔记之九 并发

    一.访问共享的可变数据时要同步 1.synchronized关键字既然保证访问的可见性也能保证原子性.而volatile修饰符只能保证变量的线程可见性. 2.增量操作符等不是原子性,多线程操作时可能导 ...

随机推荐

  1. 蓝凌表单的表体调用Javascript

    应用场景:像请假类表单会在从表输入开始时间.结束时间等字段 需求1:客户希望根据开始.结束时间自动计算小时数 解决方法: 1.主表单增加一行,设三个字段[开始时间合计].[结束时间合计].[开始结束时 ...

  2. java中解决组件重叠的问题(例如鼠标移动组件时)

    java中解决组件覆盖的问题!     有时候在移动组件的时候会出现两个组件覆盖的情况,但是你想让被覆盖的组件显示出来或者不被覆盖! 在设计GUI时已经可以定义组件的叠放次序了(按摆放组件的先后顺序) ...

  3. Android自定义之TextView跑马灯的监听

    TextView都有跑马灯的效果,如果说让你去监听跑马灯效果的执行,我觉得这个需求有点二了,但是也要实现. 思路: 1.自定义View  继承TextView   这种方法过于麻烦,只是监听一个跑马灯 ...

  4. Unity3D 游戏开发之内存优化

    项目的性能优化主要围绕CPU.GPU和内存三大方面进行. 无论是游戏还是VR应用,内存管理都是其研发阶段的重中之重. 然而,在我们测评过的大量项目中,90%以上的项目都存在不同程度的内存使用问题.就目 ...

  5. linux主机间复制文件

    命令基本格式: 1.从 本地 复制到 远程    * 复制文件:          * 命令格式:                  scp local_file remote_username@re ...

  6. 通过python切换hosts文件

    做开发或测试时常需要切换hosts ,如果hosts比较多,那么频繁的打开hosts文件对地址加注释(#),再把去掉注释是个繁琐的事情. 当然,SwitchHosts 已经可以帮我们方便的解决了这个繁 ...

  7. BABOK2主要概要输入输出图

  8. Struts 2 拦截器

    什么是Struts 2 拦截器  拦截器就是当用户请求后台Action类时在Action的Excute()方法执行前和Result返回魔板试图之后(将页面(数据)发送给浏览器渲染之前)所需要的一些通用 ...

  9. R语言简单聚类分析

    #以R基础包自带的鸢尾花(Iris)数据进行聚类分析iris data <- iris[,:] #系统聚类法(层次聚类法) distance <- dist(data) #计算距离 iri ...

  10. 关于Form.Close跟Form.Dispose

    我们在Winform开发的时候,使用From.Show来显示窗口,使用Form.Close来关闭窗口.熟悉Winform开发的想必对这些非常熟悉.但是Form类型实现了IDisposable接口,那我 ...