【转】Java并发编程注意事项

  1. 保证线程安全的三种方法:
    1. 不要跨线程访问共享变量
    2. 使共享变量是final类型的
    3. 将共享变量的操做加上同步
  2. 一开始就将类设计成线程安全的, 比在后期从新修复它,更容易.
  3. 编写多线程程序, 首先保证它是正确的, 其次再考虑性能.
  4. 无状态或只读对象永远是线程安全的.
  5. 不要将一个共享变量裸露在多线程环境下(无同步或不可变性保护)
  6. 多线程环境下的延迟加载须要同步的保护, 由于延迟加载会形成对象重复实例化
  7. 对于volatile声明的数值类型变量进行运算, 每每是不安全的(volatile只能保证可见性,不能保证原子性).详见volatile原理与技巧中, 脏数据问题讨论.
  8. 当一个线程请求得到它本身占有的锁时(同一把锁的嵌套使用), 咱们称该锁为可重入锁.在jdk1.5并发包中, 提供了可重入锁的java实现-ReentrantLock.
  9. 每一个共享变量,都应该由一个惟一肯定的锁保护.建立与变量相同数目的ReentrantLock, 使他们负责每一个变量的线程安全.
  10. 虽然缩小同步块的范围, 能够提高系统性能.但在保证原子性的状况下, 不可将原子操做分解成多个synchronized块.
  11. 在没有同步的状况下, 编译器与处理器运行时的指令执行顺序可能彻底出乎意料.缘由是, 编译器或处理器为了优化自身执行效率, 而对指令进行了的重排序(reordering).
  12. 当一个线程在没有同步的状况下读取变量, 它可能会获得一个过时值, 可是至少它能够看到那个线程在当时设定的一个真实数值. 而不是凭空而来的值. 这种安全保证, 称之为最低限的安全性(out-of-thin-air safety)

    在开发并发应用程序时, 有时为了大幅度提升系统的吞吐量与性能, 会采用这种无保障的作法.可是针对, 数值的运算, 仍旧是被否决的.java

  13. volatile变量,只能保证可见性, 没法保证原子性.
  14. 某些耗时较长的网络操做或IO, 确保执行时, 不要占有锁.
  15. 发布(publish)对象, 指的是使它可以被当前范围以外的代码所使用.(引用传递)对象逸出(escape), 指的是一个对象在还没有准备好时将它发布.

原则: 为防止逸出, 对象必需要被彻底构造完后, 才能够被发布(最好的解决方式是采用同步)编程

this关键字引用对象逸出数组

例子: 在构造函数中, 开启线程, 并将自身对象this传入线程, 形成引用传递.而此时, 构造函数还没有执行完, 就会发生对象逸出了.安全

  1. 必要时, 使用ThreadLocal变量确保线程封闭性(封闭线程每每是比较安全的, 但必定程度上会形成性能损耗)封闭对象的例子在实际使用过程当中, 比较常见, 例如 hibernate openSessionInView机制, jdbc的connection机制.
  2. 单一不可变对象每每是线程安全的(复杂不可变对象须要保证其内部成员变量也是不可变的)良好的多线程编程习惯是: 将全部的域都声明为final, 除非它们是可变的
  3. 保证共享变量的发布是安全的a, 经过静态初始化器初始化对象(jls 12.4.2叙述, jvm会保证静态初始化变量是同步的) b, 将对象申明为volatile或使用AtomicReference c, 保证对象是不可变的d, 将引用或可变操做都由锁来保护
  4. 设计线程安全的类, 应该包括的基本要素: a, 肯定哪些是可变共享变量b, 肯定哪些是不可变的变量c, 指定一个管理并发访问对象状态的策略
  5. 将数据封装在对象内部, 并保证对数据的访问是原子的.建议采用volatile javabean模型或者构造同步的getter,setter.
  6. 线程限制性使构造线程安全的类变得更容易, 由于类的状态被限制后, 分析它的线程安全性时, 就没必要检查完整的程序.
  7. 编写并发程序, 须要更全的注释, 更完整的文档说明.
  8. 在须要细分锁的分配时, 使用java监视器模式好于使用自身对象的监视器锁.前者的灵活性更好.

Object target = new Object();网络

// 这里使用外部对象来做为监视器, 而非this多线程

synchronized(target) {并发

// TODOjvm

}函数

针对java monitor pattern, 实际上ReentrantLock的实现更易于并发编程.功能上, 也更强大.性能

  1. 设计并发程序时, 在保证伸缩性与性能折中的前提下, 优先考虑将共享变量委托给线程安全的类.由它来控制全局的并发访问.
  2. 使用普通同步容器(Vector, Hashtable)的迭代器, 须要外部锁来保证其原子性.缘由是, 普通同步容器产生的迭代器是非线程安全的.
  3. 在并发编程中, 须要容器支持的时候, 优先考虑使用jdk并发容器(ConcurrentHashMap, ConcurrentLinkedQueue, CopyOnWriteArrayList...).
  4. ConcurrentHashMap, CopyOnWriteArrayList并发容器的迭代器,以及全范围的size(), isEmpty() 都表现出弱一致性.他们只能标示容器当时的一个数据状态. 没法完整响应容器以后的变化和修改.
  5. 使用有界队列, 在队列充满或为空时, 阻塞全部的读与写操做. (实现生产-消费的良好方案) BlockQueue下的实现有LinkedBlockingQueue与ArrayBlockingQueue, 前者为链表, 可变操做频繁优先考虑,后者为数组, 读取操做频繁优先考虑. PriorityBlockingQueue是一个按优先级顺序排列的阻塞队列, 它能够对全部置入的元素进行排序(实现Comparator接口)
  6. 当一个方法, 能抛出InterruptedException, 则意味着, 这个方法是一个可阻塞的方法, 若是它被中断, 将提早结束阻塞状态.当你调用一个阻塞方法, 也就意味着, 自己也称为了一个阻塞方法, 由于你必须等待阻塞方法返回.

若是阻塞方法抛出了中断异常, 咱们须要作的是, 将其往上层抛, 除非当前已是须要捕获异常的层次.若是当前方法, 不能抛出InterruptedException, 可使用Thread.currentThread.interrupt()方法, 手动进行中断.

相关文章
相关标签/搜索