1.死锁
某资源加锁后没有释放或者没有正确释放该锁,另一线程无法获取该资源锁,引起死锁。
出现死锁可以dump线程信息,查看死锁原因,从而解决。
避免死锁的常见方法
- 避免一个线程同时获取多个锁
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
- 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
- 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
2.资源限制
资源限制是指在进行并发编程时,程序的执行速度受限于计算机硬件资源或软件资源,例如,服务器的带宽只有2MB/s,某个资源的下载速度是1MB/s,系统启动10个线程下载资源,下载速度不会变成10MB/s。所有在进行并发编程时要考虑这些资源的限制。
如何解决
对于硬件资源限制,可以考虑使用集群并行执行程序,对于软件资源,可以考虑使用资源池将资源复用,在已有资源不能变的情况下,例如带宽下载,可以考虑别的方面,例如IO的读写速度。
3.volatile
- volatile变量追加字节到处理器位数(例如CPU64bit),这样变量就可以填满高速cache的缓存行,例如队列情况就可以避免头节点和尾节点加载到同一个缓存行,使头尾节点在修改时不会相互锁定
- 当把变量声明为volatile时,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序,volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型变量时总会返回最新写入的值。
并不是所有volatile变量都需要填充
- 缓存行并非64字节宽的处理器
- 共享变量不会被频繁的写
不过这种追加字节的方式在JDK7下可能无效,Java7 更加智慧,他会淘汰或重新排列无用字段,需要使用其他追加字节的方式。
不建议过度依赖volatile提供可见性
如果在代码中依赖volatile变量来控制状态的可见性,通常比使用锁的代码更脆弱,也更难理解与维护。
仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用他们。
经典用法:检查某个状态标记以判断是否退出循环,某个操作是否完成,状态标识。
锁既可以保证原子性也可以保证可见性,volatile只能保证可见性
4.无状态对象
无状态对象一定是线程安全的
@ThreadSafe
public class StatelessFactorizer implements Servlet {
public void service(ServletRequest req,ServletResponse res){
BigInteger i =extractFromRequest(req);
BigInterger[] factors = factor(i);
encodeIntoResponse(resp,factors);
}
}
StatelessFactorizer是无状态的,它既不包含任何域,也不包含任何对其他类中域的引用,计算过程中的临时状态仅存在于线程栈上的局部变量中,并且只能由正在执行的线程访问。
5.Final域
对于Final域,编译器处理器遵守的两个重排序规则
- 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作不能重排序
- 初次读一个包含final域的对象引用,与随后初次读这个final域,这两个操作不能重排序。
Final域能确保初始化过程的安全性,从而可以不受限制的访问不可变对象,并在共享这些对象时无须同步。
//线程安全
class OneValueCache {
private final BigInteger lastNumber;
private final BigInteger[]
lastFactors;
//构造函数内包含final域,保证对象初始化过程的可见性
public OneValueCache(BigInteget i,BigInteger[] factors){
lastNumber =i;
//采用副本,不然引用溢出
lastFactors = Arrays.copyOf(factors,factors.length);
}
public BigInteger[] getFactors(BigInteger i){
if(lastNumber == null || !lastNumber.equal(i))
return null;
else
return Arrays.copyOf(lastFactors,lastFactors.length);
}
}
public class VolatileCachedFactorizer implements Servlet {
private volatile OneValueCache cache = new OneValueCache(null,null);
public void service(ServletRequest req,ServletResponse resp){
BigInteger i = extractFromRequest(req);
BigInteger[] factors = cache.getFactors(i);
if(factors == null){
factors = factor(i);
//final 初始化与引用赋值不能重排序保证线程安全
cache = new OneValueCache(i,factors);
}
encodeIntoResponse(resp,factors);
}
}
任何线程都可以在不需要额外同步的情况下安全的访问不可变对象,即使在发布这些对象时没有使用同步。
6.对象的安全发布
要安全的发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见,一个正确构造的对象可以通过以下方式来安全的发布:
- 在静态初始化函数中初始化一个对象引用
- 将对象的引用保持到volatile类型的域或者AtomicRederance对象中
- 将对象的引用保存到某个正确构造对象的final类型中
- 将对象的引用保存到一个由锁保护的域中