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
70:线程安全性的文档化
71:慎用延迟初始化
72:不要依赖于线程调度器
73:避免使用线程组