Effective Java: 并发

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

简介

  1. 关键字 synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法.或者某一代码块
  2. Java 语言规范保证读或者写一个变量是原子性的,除非这个变量的类型是long或者double
  3. 为了在线程之间进行可靠的通信,也为了互斥访问,同步是必须的.
  4. volatile修饰符不执行互斥访问,但它可以保证任何一个线程在读取该域的时候都将看到最近杠杆被写入的值.
  5. 使用volatile需要格外谨慎,因为它并没有互斥作用,如果声明一个volatile int,然后对其进行++操作,那将会导致data corruption,因为++不是原子性的.
  6. 要么不共享数据,要么共享不可变数据,将可变数据限制在单个线程中.

小结

  1. 多个线程共享可变数据的时候每个读或者写数据的线程都必须执行同步.
  2. volatile是一种可接受的同步形式

67.避免过度同步

简介

  1. 过渡同步可能导致 性能降低,死锁甚至不确定的行为.
  2. 为了避免 活性失败和安全性失败,在一个被同步的方法或者代码块中,永远不要放弃对客户端的控制,即,在一个被同步的区域内部,不要滴啊用设计成要被覆盖的方法.
  3. 如果要将外来方法的调用移出同步的代码块,可以使用CopyOnWriteArrayList
  4. 通常应该在同步区域内做尽可能少的工作.

StringBuffer几乎总是被用于单个线程之中,但是他们执行的确是内部同步
因此被StringBuilder代替.

小结

  1. 为避免死锁和数据破坏,不要在同步区域内部调用外来方法.
  2. 要尽量限制同步区域内的工作量.
  3. 当设计一个可变类时,应该考虑他们是否应该自己完成同步操作.

68.executor和task优先于线程

简介

  1. Executors提供了多个工厂方法,创建ExecutorService,还可以直接使用ThreadPoolExecutor,对线程池做更精细的控制.
  2. 如果轻负载,使用Executors.newCachedThreadPool,如果重负载,用Executors.newFixedThreadPool
  3. 任务和机制被分别抽象了,前者为RunnableCallable,后者则是executor service
  4. java.util.Timer是单线程,且抛出未捕获异常会终止,可以使用ScheduledThreadPoolExecutor替代;

69.并发工具优先于wait和notify

java.util.concurrent包主要包含三块:

  • Executor Framework(任务执行工具)
  • concurrent collections(并发集合)
  • synchronizers(同步器)
concurrent collections
  1. concurrent collections提供了标准容器的高性能并发实现.内部同步和互斥,外部使用,无需加锁.
  2. 优先使用ConcurrentHashMap,而不是Collections.synchronizedMap或者Hashtable,且无需做同步操作.
  3. 有的concurrent collections提供了block操作接口,例如BlockingQueue,从中取数据的时候,如果队列为空,线程将等待,新的数据加入后,将自动唤醒等待的线程;大部分的ExecutorService都是采用这种方式实现的
synchronizers

最常用的同步器是CountDownLatchSemaphore,较不常用的是CyclicBarrierExchanger

  1. CountDownLatch 允许多个线程等待另外一个多个线程完成某种工作
  2. 如果非要用waitnotify,注意以下几点:
    • wait前的条件检查,当条件成立时,就跳过等待,可以保证不会死锁,
    • wait后的检查,条件不成立继续等待,可以保证安全
    • 通常情况下都应该使用notifyAll,虽然从优化角度看,这样不好.

建议

  1. 对于间歇式定时,应该始终使用System.nanoTime而不是System.cucurrentTimeMills
  2. 应该始终使用wait循环模式来调用wait方法.不要在循环外调用wait方法.

小结

  1. 直接使用 waitnotify,就像 用并发汇编语言进行编程一样.而concurrent则提供了更高级的语言
  2. 没有理由在新代码中使用 waitnotify ,即使有,也很少
  3. 如果正在维护使用 waitnotify的代码,则尽量在 while循环内部调用wait
  4. 应该优先使用notifyAll,而不是notify.

70.线程安全性的文档化

简介

  1. 一个方法的声明中加了synchronized并不能保证它是线程安全的,并且Javadoc也不会把这个关键字输出到文档中
  2. 一个类为了可被多个线程安全的使用,文档中必须清楚的说明它所支持的线程安全级别

线程安全级别

  1. 不可变的 — 这个类的实例是不可变得
  2. 无条件的线程安全 — 这个类的实例是可变的,但是有着足够的内部同步,类的实例无需外部同步即可被并发使用
  3. 有条件的线程安全 — 有些方法为进行安全的并发,需要使用外部同步
  4. 非线程安全 — 这个类的实例是可变的.为了并发的使用它们,客户端必须利用自己选择的外部同步包围方法调用.
  5. 线程对立的 — 这个类不能安全地被多个线程并发的使用,即使所有的方法被外部同步包围

小结

  1. 当一个类承诺了使用一个公有可访问的锁对象时,就意味着允许客户端以原子的方式执行一个方法的调用序列. 但是这种方法会发生拒绝服务攻击,只要客户端超时地持有公有可访问锁即可.
  2. 这里和 13条中,使类和成员的可访问性最小化不谋而合,要把锁对象封装在它所同步的对象中.
  3. lock 域应被声明为final,防止不小心改变它的内容.
  4. 私有锁只能用在无条件的线程安全类上.适用于专为继承而设计的类

71.慎用延迟初始化

简介

  1. 延迟到需要 域的值时 才将它初始化的行为,如果永远不需要,就永远不会被初始化.
  2. 和大多数优化一样,建议除非必要,否则不要这么做
  3. 如果域只在类的实例部分被访问,并且初始化开销很高,就值得延迟初始化
  • 大多数情况,正常的初始化要优先于延迟初始化.
  • 如果静态成员出于性能考虑需要延迟初始化,就使用lazy initialization holder class idiom
  • 出于性能考虑对实例进行延迟初始化,要使用double-check idiom(使用volatile关键字)
  • 如果不在意是否为每个线程都重新计算域的值,且域类型为基本类型(不是long或者double),可以删除volatile关键字

小结

简而言之,大多数的域应该正常的进行初始化,否则,可以参考上面的规则,进行延迟初始化

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

简介

  1. 当有多个线程可以运行时,由线程调度器决定哪些线程将会执行.以及运行多长时间
  2. 任何依赖于线程调度器来达到正确性或者性能要求的程序,很有可能都是不可移植的.
  3. 要确保可运行线程的平均数量明显多于处理器的数量

保持可运行线程数量尽可能少

  1. 让每个线程做些有意义的工作
  2. 如果 线程没有做有意义的工作.就不应该运行
  3. 不要企图使用Thread.yield修正多线程,因为Thread.yield没有可测试的语义
  4. 线程优先级是Java平台中移植性最差的部分,所以也不要用
  5. Thread.yield的唯一用途,就是在测试期间人为的增加程序的并发性

小结

  1. 不要让应用程序的并发性依赖于线程调度器
  2. 不要依赖Thread.yield和线程优先级

73.避免使用线程组

简介

  1. 除了线程,监视器之外,系统还提供了一个基本的抽象: 线程组(Thread Group).
  2. ThreadGroup是为了方便线程管理出现的,可以统一设定线程组的一些属性.
  3. 线程组可有子线程子线程组.
  4. ThreadGroup批量管理线程.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值