java高效之线程池之并发之安全队列之我见(实际应用场景篇)

   

    由于很少在社区里发表自己手敲的文章,因为水平太低(语文是数学老师教的),自惭形秽,不怕被拍砖,但怕遭鄙视,畏首畏尾的,即使胡乱说一通,也是无关技术重点的。

 

    常常在面试和被面试的时候,作为偶尔代替考官和经常被面的我,时时刻刻都会谈及到线程安全,并发,安全队列,线程池,反射技术,设计模式,场景应用举例,优先级,同步锁,架构设计要素,技术选型,比较相关技术优劣,辞职原因,期望薪酬,职业规划和目标等等。

 

    当然本文的重点还是说说线程池,并发,安全队列等应用场景,后面再各个说明。其中,谈到的相对比较多的要数多线程开发经验和线程安全等问题了。也是吾辈最为棘手的边缘技术(高手勿喷),之所以说边缘是因为在常规的一些web开发中,程序员亲身经历的相关经验的确 少之又少,一般都是技术大拿前期做好了相应框架和测试,达到了测试条件,程序员就是codeing再codeing,至于框架的优劣无法评估,其中的精妙更无从谈起。也许这段话会遭来无数的吐槽和拍砖,但是但是也烦请体谅体谅一下底层的coder一个复杂的心情。

 

    言归正传吧,首先来说说线程池和工作队列的概念。详细可以看看JERRY_XING8,文章写的非常好,言简意赅的摘录一下:

 

    场景:Web 服务器、数据库服务器、文件服务器或邮件服务器之类的许多服务器,接下来会多举例一些实际应用中的场景来详细说明一下。

    

    注:两个虚线框分别表示线程A和线程B恩能够访问的数据边界,由此可见 任务队列是线程间通信的媒介。

 

    生产者消费者模型在软件设计中是极其常见的模型,常常被用来实现对各个组件或系统解耦合。大到分布式的系统交互,小到网络层对象和应用层对象的通讯,都会应用到生产者消费者模型,在任务队列中,生产和消费的对象为“任务”。这里把任务定义为组合了数据和操作的对象。

 

        首先我们需要清楚在设计队列表时会遇到哪些挑战: 

       1)表的读写。

              由于入队列和出队列是相互影响的,在高负载下可能会导致锁竞争、事务死锁、IO超时等等。

       2)当多个接收者试图从同一队列读数据时,它们会随机地获取重复项,因而导致重复的处理过程。

              你需要在队列上实现一些高性能的行锁定,以便让并发接受器不会接收相同的数据项。

       3)队列表需要以某种顺序存储行以及以某种顺序读取行,这使得设计索引很棘手。

              队列表并不总是遵守先进先出的,有时候顺序中的消息带有更高的优先级,无论这个消息是否入队列都要先处理它。

       4)队列表需要以XML或二进制的形式序列化对象,这使得存储和重建索引很麻烦。

              你不能在队列表中重建索引,因为它包含了文本或二进制字段。因此,每过一天,数据表会变得越来越慢,最后查询会超时,你不得不关闭服务并重建索引。

       5)出队列的过程中,一批行数据被选中、被更新,然后返回数据。

              你需要一个"State"(状态)列来定义数据项的状态。出队列时,你只需选择某个状态的数据项。现在状态只有几种类型:PENDING(待定)、PROCESSING(处理中)、PROCESSED(已处理)、ARCHIVED(存档)。你不能在状态列上创建索引,因为不能提供足够的选择性,具有相同的状态的数据行有成千上万。因此,任何出队列操作都会导致集群的索引被重新扫描,这属于CPU和IO密集型操作,会产生锁竞争。

       6)在出队列的过程中,你不能仅仅移除队列表的相关行,因为这很容易导致数据表产生存储碎片。而且,你还需要重新处理订单/任务/通知做N次操作以防止这些操作在第一次中失败。这意味着存储行数据需要更长的时间、索引会持续增长以及出队列越来越慢。

       7)你必须归从入队表把处理过的数据项归档到不同的数据表或数据库,以保持主队列表的精简。这意味着需要移动大量的带有特定状态的数据行到另一个数据库。如此大的数据移动会频繁产生存储碎片,以至于降低入队列和出队列的性能。

       8)你有24×7不间断的业务。你不能停止服务再归档大量的行数据。这意味者你必须在不影响入栈和出栈通信的情况下持续的归档行数据。

   

       下面来说说场景,mvc框架中最常用的,都知道的数据库操作类Dao文件,如表A就叫aDao这样的bean存在。比如说数据库的锁机制,级别较高,查询锁,如果在一个原子事务操作中,用到表A,这时事务完成之前,A表是被锁住的。此时又来了一个线程,也用到A表操作,也用到aDao这个实例化对象,此事,查询都超时了,超时出错了。该怎么办?这里的aDao就是图中的线程B操作,是执行任务的,线程A就是本实例的业务请求。如果,假如线程A这样的任务成千上万个,aDao在spring的声明是单例模式,那么在上下文环境中,aDao始终只有一个实例化的对象,如果说,用到队列,在此实例中如何应用?如果不用,该怎么来解决这个问题?

 

    实例分析:

    1、在最常见的本例中,涉及到数据库表操作,表的操作事务级别较高,只要一发生,立刻加锁直到事务结束。此时如果采用线程池,形成多个(或最优化的个数)aDao实例,这么多执行任务线程,在对A表操作的时候,同样需要等待排队,唤醒,终止,有瓶颈存在。因此线程池byebye!

 

        2、牺牲事务的原子性,就事务定义在Dao层,出错不能自动回滚,代码中估计自己定义手动回滚。此时,Dao的操作单个任务处理的时间很短,任务处理比较快,可复用。但是业务上尽量避免脏读赃写等操作。这时可用线程池,这里的线程池如何应用?

 

        3、将队列放到业务操作上,也即将事务原子操作作为队列,等待一个业务操作完毕,再进行下一个业务操作,可以,但是不符合耦合性。pass!

        

        针对第二点,引出了一个新的命题,线程池何时用,该怎么用?(以下摘自kavensu

        

         1.单个任务处理的时间比较短 

         2.将需处理的任务的数量大(大到什么程度,这里估算在每秒几百到几千个任务)  这样其好处也是显而易见的,        1.减少在创建和销毁线程上所花的时间以及系统资源的开销 

猜你喜欢

转载自coral0212.iteye.com/blog/2171108
今日推荐