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. VS2012未找到与约束ContractName...匹配的导出

    用VS2012创建ARCGIS插件项目时,提示“未找到与约束ContractName...匹配的导出”,此前一直都是正常的额 经查,发现是近期系统相关更新导致,解决办法有两种途径: 一是删除近期更新的 ...

  2. Reveal查看任意app的高级技巧(转)

    原文:http://zhuanlan.zhihu.com/iOSRe/19646016 Reveal查看任意app的高级技巧 hangcom · 12 小时前 Reveal是一个很强大的UI分析工具, ...

  3. shell 和awk性能对比

    time for ((i=0;i<10000;i++)) do ((sum+=i)); done real    0m0.086suser    0m0.079ssys    0m0.007s ...

  4. css3,background-clip/background-origin的使用场景,通俗讲解

    先不说background-clip/background-origin的用法,我们先来聊聊css背景方面的知识. <!DOCTYPE html> <html lang=" ...

  5. ruby -- 进阶学习(十一)配置解决production环境下无法加载css或js

    最近配置production环境,找了好几份文档,从傻逼到苦逼~~终于配置成功~~@_@!!! 首先,先加载以下几个插件: # Use Uglifier as compressor for JavaS ...

  6. css - position relative与display table-cell深入分析

    在很多时候,想使用 display: table; 以及其子元素使用 display: table-cell 都只是为了使用 vertical-align 这一属性.这是一个很简单的常识,但这次在项目 ...

  7. 转载--CentOS 6.3下部署LVS(NAT)+keepalived实现高性能高可用负载均衡

    源地址:http://www.cnblogs.com/mchina/archive/2012/08/27/2644391.html 一.简介 VS/NAT原理图: 二.系统环境 实验拓扑: 系统平台: ...

  8. spring事务与消息队列

    在开发过程中,遇到一个bug,产生bug的原因是spring事务提交晚于消息队列的生产消息,导致消息队列消费消息时获取到的数据不正确.这篇文章介绍问题的产生和一步步的解决过程. 一.问题的产生: 场景 ...

  9. Entity FrameWork 延迟加载本质(二)

    1.对于外键实体而言,EF会在用到这个外键属性的时候,才会去查对应的表.这就是按需加载了... 2.按需加载的缺点:每次调用外键实体的时候,都会去查询数据库(EF有小优化:相同的外键实体只查一次) I ...

  10. 2015年百度之星初赛(1) --- B 找连续数

    找连续数 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...