java 多线程注意事项

转自:https://blog.csdn.net/kkgbn/article/details/56279659

java 多线程注意事项

一,线程池的概念

线程池的做用:

线程池做用就是限制系统中执行线程的数量。
     根据系统的环境状况,能够自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了形成系统拥挤效率不高。用线程池控制线程数量,其余线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务须要运行时,若是线程池中有等待的工做线程,就能够开始运行了;不然进入等待队列。

为何要用线程池:

1.减小了建立和销毁线程的次数,每一个工做线程均可以被重复利用,可执行多个任务。
2.能够根据系统的承受能力,调整线程池中工做线线程的数目,防止由于消耗过多的内存,而把服务器累趴下(每一个线程须要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Java里面线程池的顶级接口是Executor,可是严格意义上讲Executor并非一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。


二,线程资源必须经过线程池提供,不容许在应用中自行显式建立线程

说明:使用线程池的好处是减小在建立和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。
若是不使用线程池,有可能形成系统建立大量同类线程而致使消耗完内存或者“过分切换”的问题。


三,SimpleDateFormat 是线程不安全的类,通常不要定义为static变量,若是定义为static,必须加锁,或者使用DateUtils工具类。 

正例:注意线程安全,使用DateUtils。亦推荐以下处理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
说明:若是是JDK8的应用,可使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替Simpledateformatter,
官方给出的解释:simple beautiful strong immutable thread-safe。


四,高并发时,同步调用应该去考量锁的性能损耗。

能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。


五,对多个资源、数据库表、对象同时加锁时,须要保持一致的加锁顺序,不然可能会形成死锁。 

说明:线程一须要对表A、B、C依次所有加锁后才能够进行更新操做,
那么线程二的加锁顺序也必须是A、B、C,不然可能出现死锁。


六,并发修改同一记录时,

避免更新丢失,要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version做为更新依据。 说明:若是每次访问冲突几率小于20%,推荐使用乐观锁,不然使用悲观锁。乐观锁的重试次数不得小于3次。


七,多线程并行处理定时任务时,

Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。


八,使用CountDownLatch进行异步转同步操做,

每一个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法能够执行,
避免主线程没法执行至countDown方法,直到超时才返回结果。 说明:注意,子线程抛出异常堆栈,不能在主线程try-catch到。


九,避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed 致使的性能降低。

说明:Random实例包括java.util.Random 的实例或者 Math.random()实例。 
正例:在JDK7以后,能够直接使用API ThreadLocalRandom,在 JDK7以前,能够作到每一个线程一个实例。


十,经过双重检查锁(double-checked locking)(在并发场景)实现延迟初始化的优化问题隐患

(可参考 The "Double-Checked Locking is Broken" Declaration),推荐问题解决方案中较为简单一种(适用于JDK5及以上版本),将目标属性声明为 volatile型。
 反例:
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) synchronized(this) {
if (helper == null)
helper = new Helper();
}
     return helper;
}
// other functions and members...
}


十一,volatile解决多线程内存不可见问题。

对于一写多读,是能够解决变量同步问题,可是若是多写,一样没法解决线程安全问题。
若是是count++操做,使用以下类实现:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 
若是是JDK8,推荐使用LongAdder对象,比AtomicLong性能更好(减小乐观锁的重试次数)。




十二,HashMap在容量不够进行resize时因为高并发可能出现死链,致使CPU飙升,在开发过程当中注意规避此风险。



十三,ThreadLocal没法解决共享对象的更新问题,

ThreadLocal对象建议使用static修饰。这个变量是针对一个线程内全部操做共有的,因此设置为静态变量, 全部此类实例共享此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,全部此类的对象(只要是这个线程内定义的)均可以操控这个变量。
相关文章
相关标签/搜索