讨论并发
第七十八条 ,同步方法共享的可变数据
1.java语言规范保证读或者写一个变量是原子性的,但是不是可见性的,一个线程修改,另一线程不一定知道这个
变量被修改了(java内存模型,每个线程都有自己内存)
2.为了在线程之间进行可靠的通信,也为了互斥访问,同步是必要的
3.千万不要使用thread.stop
4.除非读和写都被同步,否则无法保证同步能起作用
5.volatile是可见的,但不是原子的
6.++操作符,不是原子的
7.可以使用原子包 java.util.concurrent.atomic
8.将可变的数据限制在单个线程中,可以把一个可变的对象放到静态域中,作为类初始化的一部分,可以
设置volatile,final或者正常加锁,只要这个对象不再变化,这称为高效不可变
9.当多个线程共享可变数据的时候,每个读或者写的线程都必须执行同步
第七十九条,避免过度同步
1.过度同步可能导致性能降低,死锁,甚至不确定的行为
2.为了避免活性失败和安全失败,在一个被同步的方法或代码块中,永远不要放弃对客户端的控制,换句话来说
在一个同步方法的区域内部,不要调用设计成为要被覆盖的方法,或者由客户端以函数对象的形式提供的方法
从包含该同步方法的类的角度来看,这样的方法是外来的,这个类不知道该方法做什么,也无法控制它,根据
外来方法的作用,从同步区域中调用它会导致异常,死锁或者数据损坏
2.通常来说,应该在同步区域内做尽可能少的工作
3.例子: /chapter11/sample79/ForwardingSet
第八十条,executor,task和stream优先于线程
1.Executors类是使用,Executors.newCachedThreadPool (不够则创建新的线程),Executors.newFixedThreadPool(固定线程),定制
可以直接创建ThreadPoolExecutor
2.Runnable,没有返回值,Callable有返回值
3.特殊线程池ForkJoinPool,以及任务ForkTask,不仅需要处理任务,还需要去抢另一个线程的任务,让所以的线程都忙碌
第八十一条,并发工具优先于wait和notify
1.既然正确地使用wait和notify比较困难,就应该用更高级的并发工具来代替
2.并发合集中不可能排除并发活动,将它锁定没有什么作用,只会使程序的速度变慢,内部使用同步
3.优先使用ConcurrentHashMap,而不是使用 Collections.synchronizedMap()
4.BlockingQueue扩展可Queue接口,队列为空则等待,生产-消费模式
5.同步器是使线程能够等待另一个线程的对象,允许他们协调动作,常用的CountDownLatch,Semaphore,不常用
的CyclicBarrier和 Exchanger,功能强大的同步器Phaser
6.对于间歇式的定时,始终应该优先使用System.nanoTime(),而不是System.currentTimeMillis(),nanoTime更准确,更精确
7.始终应该使用while循环模式来调用wait方法,永远不要在循坏之外调用wait方法,优先使用notifyAll,而不是notify
8.没有理由在新的代码中使用wait方法和notify方法,即使有,也是极少的
第八十二条,线程安全性的文档化
1.javadoc并没有在它的输出中包含synchronized修饰符,因为在一个方法声明中出现synchronized修饰符,这是个实现细节
并不是导出API的一部分
2.一个类为了可被多个线程安全的使用,必须在文档中清除地说明它所支持的线程安全性级别
3.线程安全性级别
1.不可变的-这个类是不可变的,外部不需要同步,String,Long,BigInteger
2.无条件的线程安全-这个类是可变的,但是这个类有着足够的内部同步,外部不需要同步,AtomicLong和
ConcurrentHashMap
3.有条件的线程安全-除了有些方法为进行安全的并发使用而需要外部同步之外,这种线程安全级别与无条件的
线程安全相同, Collections.synchronizedMap(),它的迭代器需要外部同步
4.非线程安全的-这个类的实例是可变的,需要外部进行同步,ArrayList和HashMap
5.线程对立的-这种类不能安全的被多个线程并发使用,即使所有的方法调用都被外部同步包围,线程对立的根源
没有同步的修改静态数据
6.有条件的线程安全的类,在文档中需要说明,那个调用需要外部同步,还有同步需要那一把锁
7.私有锁对象模式只适合与无条件的线程安全模式和为了继承而设计的类,私有锁:在对象内部定义一个
私有的final的变量保存锁
第八十三条,慎用延迟初始化
1.延迟初始化是指延迟到需要域的值时才将它初始化的行为,如果不需要这个值,那么永远都不会初始化,这是一种
优化,单挑==但它也可以用来破坏类中有害循环和实例初始化
2.延迟初始化除非绝对必要,否则不要这么做
3.大多数情况下,正常初始化要优先于延迟初始化
4.延迟初始化安全的做法
1.如果利用延迟优化来初始化一个域,就要使用同步方法来访问
private FiledType field;
private synchronized FiledType getFiled(){
field = computeFieldValue();
return field;
}
2.如果处于性能的考虑而需要对静态域使用延迟初始化,就要使用jvm加载类机制
private static class FieldHolder{
static final FiledType field = computeFieldValue();
}
private static fieldType getField(){
//jvm虚拟机会线程安全的加载类并且初始化field
return FieldHolder. field;
}
3.如果处于性能考虑而需要对实例域使用延迟初始化,就使用双重检查模式
//volatile保证field线程可见
private volatile FieldType field;
private FiledType getFiled(){
FiledType result =field;
//初始化后不需要锁
if(result == null){
//未初始化后需要锁
synchronized(this){
field = result = computeFieldValue();
}
}
return result;
}
//如果可以接收重复初始化,则可以变成单重检查模式
private FiledType getFiled(){
FiledType result =field;
if(result == null){
field = result = computeFieldValue();
}
return result;
}
4.如果不介意在每个线程都初始化一次,那么可以去掉volatile,这种方法在一些特别的jvm虚拟机上加快域的访问,但是
不适合日常的使用,不推荐
第八十四条,不要依赖于线程调度器
1.任何依赖于线程调度器来达到正确性或者性能要求的程序,很有可能都是不可移植的
2.如果线程没有在做有意义的工作,就不应该运行
3.忙-等这种做法也会极大的增加处理器的负担
4.不要企图通过Thread.yield来修正该程序,yield让出cup执行,但是让不让出,能不能获得cup执行都无法保证
5.线程优先级是java平台上最不可移植的特征,每个操作系统不一样
网友评论