现在的位置: 首页 > 综合 > 正文

JAVA多线程编程陷阱总结三

2013年10月14日 ⁄ 综合 ⁄ 共 2084字 ⁄ 字号 评论关闭

陷阱9volatile只能强调数据的可见性,并不能保证原子操作和线程安全

volatile只能强调数据的可见性,并不能保证原子操作和线程安全,因此volatile不是万能的。参考指令重排序

volatile最常见于下面两种场景。

a. 循环检测机制

volatile boolean done = false;

    while( ! done ){
        dosomething();
    }


b. 单例模型 (http://www.blogjava.net/xylz/archive/2009/12/18/306622.html)

 陷阱10Lock不可以完全替换synchronized

看起来Lock有更好的性能以及更灵活的控制,是否完全可以替换synchronized?

锁的一些其它问题中说过,synchronized的性能随着JDK版本的升级会越来越高,而Lock优化的空间受限于CPU的性能,很有限。另外JDK内部的工具(线程转储)对synchronized是有一些支持的(方便发现死锁等),而对Lock是没有任何支持的。

也就说简单的逻辑使用synchronized完全没有问题,随着机器的性能的提高,这点开销是可以忽略的。而且从代码结构上讲是更简单的。简单就是美。

对于复杂的逻辑,如果涉及到读写锁、条件变量、更高的吞吐量以及更灵活、动态的用法,那么就可以考虑使用Lock。当然这里尤其需要注意Lock的正确用法。

Lock lock = 
lock.lock();
try{
    //do something
}finally{
    lock.unlock();
}


一定要将Lock的释放放入finally块中,否则一旦发生异常或者逻辑跳转,很有可能会导致锁没有释放,从而发生死锁。而且这种死锁是难以排查的。

如果需要synchronized无法做到的尝试锁机制,或者说担心发生死锁无法自恢复,那么使用tryLock()是一个比较明智的选择的。

Lock lock = 
if(lock.tryLock()){
    try{
        //do something
    }finally{
        lock.unlock();
    }
}

甚至可以使用获取锁一段时间内超时的机制Lock.tryLock(long,TimeUnit)。 锁的使用可以参考前面文章的描述和建议。


陷阱11:并不是线程数越多越好

并不是线程数越多越好,在下一篇文章里面会具体了解下性能和可伸缩性。 简单的说,线程数多少没有一个固定的结论,受限于CPU的内核数,IO的性能以及依赖的服务等等。因此选择一个合适的线程数有助于提高吞吐量。

对于CPU密集型应用,线程数和CPU的内核数一致有助于提高吞吐量,所有CPU都很繁忙,效率就很高。 对于IO密集型应用,线程数受限于IO的性能,某些时候单线程可能比多线程效率更高。但通常情况下适当提高线程数,有利于提高网络IO的效率,因为我们总是认为网络IO的效率比较低。

对于线程池而言,选择合适的线程数以及任务队列是提高线程池效率的手段。

public ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    ThreadFactory threadFactory,
    RejectedExecutionHandler handler)

 


对于线程池来说,如果任务总是有积压,那么可以适当提高corePoolSize大小;如果机器负载较低,那么可以适当提高maximumPoolSize的大小;任务队列不长的情况下减小keepAliveTime的时间有助于降低负载;另外任务队列的长度以及任务队列的拒绝策略也会对任务的处理有一些影响。

抱歉!评论已关闭.