并发技巧清单

如何尽量保证线程安全

避免死锁

1.对于资源的加锁时间必须足够短,也就是必要时进行锁
2.访问资源过程中的锁需要按照一致的顺序进行获取,否则需要提升出一个更大的锁来确保资源的获取
3.尽量通过封装的形式,避免将锁暴露给外部,从而造成不必要的资源死锁
4.多数情况下,死锁是由于获取锁的顺序错误锁导致的。
5.避免一个线程同时获取多个锁。
6.避免一个线程在锁内同时占用多个资源,尽量保持一个锁只占用一个资源。
7.尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。 8.

原子性

原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为"不可被中断的一个或一系列操作" 。 在多处理器上实现原子操作就变得有点复杂。

对象类型

基本类型

可见性

final

volatile

synchronized

happen before

可排序性

Happen Before法则

如果多个线程访问同一个可变的状态变量时,没有使用合适的同步,那么程序就会出现错误。有三种方式可以修复这个问题:

  1. 不在线程之间共享该状态变量
  2. 将状态变量修改为不可变的变量
  3. 在访问状态变量时使用同步

什么是线程安全性

在线程安全性的定义中,最核心的概念就是正确性。 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调用代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

在线程安全类中封装了必要的同步机制,因此客户端无需进一步采取同步措施。

无状态对象一定是线程安全的。

熟练使用线程安全类

要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

并非所有的数据都需要锁的保护,只有被多个线程同时访问的可变数据才需要通过锁来保护。

对非原子的64位操作,如long,double类型的变量,在多线程下存在这样的共享变量时,请把变量定义成volatile

加锁机制既可以确保可见性又可以确保原子性 ,volatile变量只可以确保可见性。

当访问共享的可变数据时,通常需要同步,一种避免使用同步方式就是不共享数据。

满足不可变对象的条件:

final域能确保初始化过程中的安全性

不变模式

在并行程序开发过程中,同步操作似乎是必不可少的。当多线程对同一个对象进行读写操作时,为了保证数据对象对一致性 和正确性,有必要对对象使用同步。故而同步操作对性能有相当对消耗。为了尽可能的去除这些同步操作,提高并行程序的 性能,可以使用一种不可变的对象,依靠对象的不变性,可以确保其在没有使用同步操作的多线程环境下依然始终保持 内部状态的一致性和正确性。

不变模式天生就是多线程友好的,它的核心思想是,一个对象一旦被创建,则它的内部状态将永远不会发生改变。所以,没有 一个线程可以修改其内部状态和数据。

不变模式使用场景介绍

JAVA的并发控制方式

隐藏比较深的数据竞争

竞争情况描述 线程T1 线程T2
编译器将这个表达式扩展成temp=x ,和 x=temp+1 x+=1 x+=2
下标i和j相同时可能会出现数据竞争 a[i]+=1 a[j]+=1
指针q和p指向同一个目标时可能会出现数据竞争 *q+=2 *p+=1
foo函数可能使用参数对一个共享变量进行修改 foo(1) foo(2)
即使在指令集,硬件还是会将【edi】对更新操作扩展成独立对对读操作和写操作改 add [edi],1 add [edi] ,2

{"class":"com.jd.jr.cupid.zebra.api.vo.QueryFeatureRequest","resIdList":["4162","4277","4246","4244","4256","4248","4245","4230","4177","4253","4176","4237","4172","4171","4239","4235","4236","4121","4165","4166"],"modelId":1,"needFeatureNum":38,"timeout":-1,"userId":"xiaoguizi001","featureDimKey":"REC_USERRES${userId}_${resId}","attrTimeKey":"CLICK_M_5"}