`
wbj0110
  • 浏览: 1556742 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

常见的并发陷阱

    博客分类:
  • Java
阅读更多

常见的并发陷阱

volatile

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

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

a. 循环检测机制

volatile boolean done = false;


    while( ! done ){
        dosomething();
    }


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

 


synchronized/Lock

看起来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)。 锁的使用可以参考前面文章的描述和建议。

 

锁的边界

一个流行的错误是这样的。

ConcurrentMap<String,String> map = new ConcurrentHashMap<String,String>();

if(!map.containsKey(key)){
    map.put(key,value);
}


看起来很合理的,对于一个线程安全的Map实现,要存取一个不重复的结果,先检测是否存在然后加入。 其实我们知道两个原子操作和在一起的指令序列不代表就是线程安全的。 割裂的多个原子操作放在一起在多线程的情况下就有可能发生错误。

实际上ConcurrentMap提供了putIfAbsent(K, V)的“原子操作”机制,这等价于下面的逻辑:

if(map.containsKey(key)){
    return map.get(key);
}else{
    return map.put(k,v);
}


除了putIfAbsent还有replace(K, V)以及replace(K, V, V)两种机制来完成组合的操作。

提到Map,这里有一篇谈HashMap读写并发的问题。

 

构造函数启动线程

下面的实例是在构造函数中启动一个线程。

public class Runner{
   int x,y;
   Thread thread;
   public Runner(){
      this.x=1;
      this.y=2;
      this.thread=new MyThread();
      this.thread.start();
   }
}


这里可能存在的陷阱是如果此类被继承,那么启动的线程可能无法正确读取子类的初始化操作。

因此一个简单的原则是,禁止在构造函数中启动线程,可以考虑但是提供一个方法来启动线程。如果非要这么做,最好将类设置为final,禁止继承。

 

丢失通知的问题

这篇文章里面提到过notify丢失通知的问题。

对于wait/notify/notifyAll以及await/singal/singalAll,如果不确定到底是否能够正确的收到消息,担心丢失通知,简单一点就是总是通知所有。

如果担心只收到一次消息,使用循环一直监听是不错的选择。

非常主用性能的系统,可能就需要区分到底是通知单个还是通知所有的挂起者。

 

线程数

并不是线程数越多越好,在下一篇文章里面会具体了解下性能和可伸缩性。 简单的说,线程数多少没有一个固定的结论,受限于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的时间有助于降低负载;另外任务队列的长度以及任务队列的拒绝策略也会对任务的处理有一些影响.

 

http://www.blogjava.net/xylz/archive/2011/12/30/367592.html

分享到:
评论

相关推荐

    汪文君高并发编程实战视频资源下载.txt

     高并发编程第三阶段21讲 Exchanger工具的使用以及常见问题分析-上_.mp4  高并发编程第三阶段22讲 Exchanger工具的使用以及常见问题分析-下_.mp4  高并发编程第三阶段23讲 Semaphore工具的介绍以及借助于...

    汪文君高并发编程实战视频资源全集

     高并发编程第三阶段21讲 Exchanger工具的使用以及常见问题分析-上_.mp4  高并发编程第三阶段22讲 Exchanger工具的使用以及常见问题分析-下_.mp4  高并发编程第三阶段23讲 Semaphore工具的介绍以及借助于...

    通向Golang的捷径【16. 常见的陷阱和误用】

    在之前的章节中, 对一些误用给出了提示, 为了避免让用户在不同的章节中, 查找上述提示, 以下给出了 Go 语言的一些常见陷阱, 以方便查找: • 不要使用类似于 var p*a 的声明, 因为这将与指针声明和乘法操作相冲突 ...

    Go语言教程.docx

    Go语言,又称Golang,是由...goroutines可以低成本地创建大量并发执行单元,而channels则提供了安全的同步和通信机制,避免了常见的并发陷阱。 自动垃圾回收:Go语言采用了基于三色标记-清除算法的垃圾回收机制,自动

    C++常见内存错误详解

    随着诸如代码重构和单元测试等方法引入实践,调试技能渐渐弱化了,甚至有人主张废除调试器。...前事不忘,后世之师,了解这些常见的错误,在编程时就加以注意,把出错的概率降到最低,可以节省不少时间

    lua-nginx-openresty-redis 详细案例源码

    Nginx 陷阱和常见错误 TCP和UDP负载平衡官方参考文档 Nginx 高并发系统内核优化 nginx 并发数问题思考:worker_connections,worker_processes与 max clients 如何在工作中提高Ngixn服务器性能?达到高效 并发 =...

    Java学习资料汇集(书籍、文章、总结)

    这本书主要讲述了一些Java开发的最佳实践和常见陷阱,帮助读者写出更高效、可读性更好的Java代码。 第三本推荐的书籍是《Java并发编程实战》。在多核和分布式时代,掌握并发编程是每个Java程序员必备的技能。这...

    Oracle 9i10g编程艺术

    锁和锁存,事务、并发和多版本,表和索引,数据类型,以及分区和并行,并充分利用具体的例子来介绍每个特性,不仅讨论了各个特性是什么,还说明了它是如何工作的,如何使用这个特性来实现软件,以及有关的常见陷阱。...

    Oracle 9i & 10g编程艺术:深入数据库体系结构

    锁和锁存,事务、并发和多版本,表和索引,数据类型,以及分区和并行,并充分利用具体的例子来介绍每个特性,不仅讨论了各个特性是什么,还说明了它是如何工作的,如何使用这个特性来实现软件,以及有关的常见陷阱。...

    Oracle.9i&10g;编程艺术深入数据库体系 part1

    锁和锁存,事务、并发和多版本,表和索引,数据类型,以及分区和并行,并充分利用具体的例子来介绍每个特性,不仅讨论了各个特性是什么,还说明了它是如何工作的,如何使用这个特性来实现软件,以及有关的常见陷阱。...

    Oracle 编程艺术深入数据库体系结构(第2版)

    事务、并发和多版本,表和索引,数据类型,分区和并行,以及数据加密等,并利用具体的例子来全面介绍每个特性,不仅讨论了各个特性是什么,还说明了它是如何工作的,如何使用这个特性来开发软件,以及有关的常见陷阱...

    Oracle编程艺术:深入理解数据库体系结构 第二版

    包括文件、内存结构和进程、锁和闩、事务、并发和多版本、表和索引、数据类型、分区和并行,以及数据加载和卸载,并利用具体的例子来全面介绍每个特性,不仅讨论了各个特性是什么,还说明了它是如何工作的,如何使用...

    Oracle.9i&10g;编程艺术深入数据库体系 part4

    锁和锁存,事务、并发和多版本,表和索引,数据类型,以及分区和并行,并充分利用具体的例子来介绍每个特性,不仅讨论了各个特性是什么,还说明了它是如何工作的,如何使用这个特性来实现软件,以及有关的常见陷阱。...

    Oracle 9i10g11g编程艺术深入数据库体系结构 中文第二版

    并利用具体的例子来全面介绍每个特性,不仅讨论了各个特性是什么,还说明了它是如何工作的,如何使用这个特性来开发软件,以及有关的常见陷阱。  本书面向所有oracle 数据库应用开发人员和dba。

    Oracle.9i&10g;编程艺术深入数据库体系 part2

    锁和锁存,事务、并发和多版本,表和索引,数据类型,以及分区和并行,并充分利用具体的例子来介绍每个特性,不仅讨论了各个特性是什么,还说明了它是如何工作的,如何使用这个特性来实现软件,以及有关的常见陷阱。...

    Oracle.9i&10g;编程艺术深入数据库体系 patr3

    锁和锁存,事务、并发和多版本,表和索引,数据类型,以及分区和并行,并充分利用具体的例子来介绍每个特性,不仅讨论了各个特性是什么,还说明了它是如何工作的,如何使用这个特性来实现软件,以及有关的常见陷阱。...

    Oracle Database 9i 10g 11g编程艺术 深入数据库体系结构

    并利用具体的例子来全面介绍每个特性,不仅讨论了各个特性是什么,还说明了它是如何工作的,如何使用这个特性来开发软件,以及有关的常见陷阱。 本书面向所有Oracle 数据库应用开发人员和DBA。

    Oracle Database 9i 10g 11g编程艺术 深入数据库体系结构 第2版

    并利用具体的例子来全面介绍每个特性,不仅讨论了各个特性是什么,还说明了它是如何工作的,如何使用这个特性来开发软件,以及有关的常见陷阱。 本书面向所有Oracle 数据库应用开发人员和DBA。

    oracle 10g 编程艺术

    锁和闩,事务、并发和多版本,表和索引,数据类型,以及分区和并行,并利用具体的例子来充分介绍每个特性,不仅讨论了各个特性是什么,还说明了它是如何工作的,如何使用这个特性来开发软件,以及有关的常见陷阱。...

    Rust程序设计语言资源合集 完整版pdf

    想要 “深入” 底层控制的程序员可以使用 Rust,无需冒着常见的崩溃 或安全漏洞的风险,也无需学习时常改变的工具链的最新知识。其语言本身更是被设计为自然而然的引导你编写出在运行速度和内存使用上都十分高效的...

Global site tag (gtag.js) - Google Analytics