一、初识Concurrent
第一次看见concurrent的使用是在同事写的一个抽取系统代码里,当时这部分代码没有完成,有许多的问题,另一个同事接手了这部分代码的功能开发,由于他没有多线程开发的经验,所以我就一起帮着分析。最开始看到这个时很烦燥啊,因为自己接触java时间很短,连synchronized都不知道怎么用呢,突然发现有这么个复杂的东西。当时就只好开始学习吧,毕竟是使用嘛,第一目的就是了解清楚这玩意的各个类与方法都干嘛用的,然后看了看同事的代码大概也就清楚了。感觉这和大部分人一样,能用就行。
下面是一段其中的应用
public class VoiceExtractRunnable implements Runnable { @Override public void run() { // 创建线程池 ExecutorService service = Executors.newCachedThreadPool(); // 声明保存各任务(线程)执行结果的集合 List<Future<VoiceExtractExpertInRuleVO>> futures = new ArrayList<Future<VoiceExtractExpertInRuleVO>>( rules.size()); // 循环提交任务 for (VoiceExtractExpertInRuleVO ruleVO : rules) { ruleVO.setExtractParamVO(vo); futures.add(service.submit(new VoiceRuleCallable(ruleVO))); } for (Future<VoiceExtractExpertInRuleVO> f : futures) { try { VoiceExtractExpertInRuleVO r = f.get(); returnRules.add(r); // 获取任务执行完成后的返回结果 } catch (Exception e) { logger.error("", e); } } // 所有任务都执行完毕后,关闭线程池 service.shutdown(); } }
看完这个代码(省略了大部分的业务逻辑代码)就能感觉到一个优点,就是线程的执行和结果获取是可以异步的,这样对于开发来说确实是有很大的帮助。代码结构也比较清晰。
但这个时候我主要还是在能用就行的阶段,而且也并不关心concurrent里到底有多少重要的代码。最近在学习JAVA的基础知识,看到线程安全的时候Concurrent开始进入我的视野,这时我才知道它原来是这么丰富,所以才开始一点点的了解。这个过程中也一直感觉到写这些代码的工程师确实厉害,能在实践的过程中总结出这么好的代码,供广大的开发们使用。
二、主要的类
Executor :具体Runnable任务的执行者。
ExecutorService :一个线程池管理者,其实现类有多种,我会介绍一部分。我们能把Runnable,Callable提交到池中让其调度。
Semaphore :一个计数信号量
ReentrantLock :一个可重入的互斥锁定 Lock,功能类似synchronized,但要强大的多。
Future :是与Runnable,Callable进行交互的接口,比如一个线程执行结束后取返回的结果等等,还提供了cancel终止线程。
BlockingQueue :阻塞队列。
CompletionService : ExecutorService的扩展,可以获得线程执行结果的
CountDownLatch :一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
CyclicBarrier :一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点
Future :Future 表示异步计算的结果。
ScheduledExecutorService :一个 ExecutorService,可安排在给定的延迟后运行或定期执行的命令。
上面是一些常用类,参考这个文章,写的比较清楚:http://www.cnblogs.com/aurawing/articles/1887056.html
我个人看了代码后对下面的这些类印象比较深刻:
ConcurrentHashMap:支持并发的hashMap
AbstractQueuedSynchronizer:提供了同步锁的基础功能实现,包含独占锁和共享锁
对于ConcurrentHashMap这个类是一种适应于并发场景下的hashMap,是建立在分离锁基础上。其内部结构可以划分为N个段,每个段都有自己的并发锁,这样写入时可以写入不同的段中,从而提高了并发的性能。
参考:http://blog.csdn.net/fw0124/article/details/43308193
AbstractQueuedSynchronizer是Concurrent包中对锁的关键抽象实现,主要是提供了一个state字段用于控制并发,通过控制state的原子状态从而保证多线程时的锁机制。这个类是抽象类,很多的场景实现需要在特定的子类中实现。
参考:
http://www.infoq.com/cn/articles/jdk1.8-abstractqueuedsynchronizer
http://www.infoq.com/cn/articles/java8-abstractqueuedsynchronizer
三、学习到的一些知识点
这几天在看源代码中也不断的在网上查找各种资料,还是学到了不少东西。
分拆锁(lock spliting)就是若原先的程序中多处逻辑都采用同一个锁,但各个逻辑之间又相互独立,就可以拆(Spliting)为使用多个锁,每个锁守护不同的逻辑。
分拆锁有时候可以被扩展,分成可大可小加锁块的集合,并且它们归属于相互独立的对象,这样的情况就是分离锁(lock striping)。(摘自《Java并发编程实践》)
对于分离锁有个更好些的解释:分拆锁有时候可以被扩展,分成若干加锁块的集合,并且它们归属于相互独立的对象,这样的情况就是分离锁。例如,ConcurrentHashMap 的实现使用了一个包含 16 个锁的数组,每一个锁都守护 HashMap 的 1/16 。假设 Hash 值均匀分布,这将会把对于锁的请求减少到约为原来的 1/16 。这项技术使得 ConcurrentHashMap 能够支持 16 个的并发 Writer 。当多处理器系统的大负荷访问需要更好的并发性时,锁的数量还可以增加。——摘自developerworks
sun.misc.Unsafe 是一个封装了很多底层操作的类,但是网上没找到太多的资料,但在Concurrent包中用的比较多,最为关键的是其提供的方法compareAndSwap之类的方法是原子的,可以不用自己加锁。看了Concurrent包中的锁主要是通过这个方法来实现的锁状态管理。
但网上也提到他可以操作内存,也难怪叫Unsafe这名,如果在java代码里随便用的话那Java不就变成和C++差不多了,呵呵。所以除了在JDK里的单元,自己写的代码中不能直接使用这个类。
volatile关键字:这个关键字是要求多线程环境下访问受volatile修改的共享数据时具有可见性。
我确实不知道怎么解释它,推荐两篇不错的文章:
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
http://www.cnblogs.com/dolphin0520/p/3920373.html
四、小感慨
在看着Concurrent包里的代码时,确实对一些精秒的设计很感叹,比如锁的设计,一种抽象与实现结合的良好设计。里面的许多小细节都体现了技术的功底,反想自己为什么设计不出这样的代码。
我感觉两方面:
1、没有实际的问题要去解决
比如Concurrent这里面的代码针对并发的编程,说实话工作中遇到的不多,一般的情况用用synchronized也是可以解决的,以前在.net里也就用用lock关键字。delphi用的时候也简单。所以没有一个好的工作场景让你去解决这些问题,说实话想都想不到。
2、基础不扎实
其实看了许多代码都是些基础应用,你说流、文件、并发这些东西都是计算机里都要面对的问题,只要掌握了这些知识,其实在实际遇到问题的时候就可以用上了。否则就会当作难题用一些其他方法规避掉,反而失去了写出更好代码的机会,时间长了就变的平庸
以后还是要多多努力学习基础,这一段时间以来我觉得自己可以在编程上有更多的收获,或许我真的能写代码到50岁,至少我觉得50岁的时候还是可以跟上时代,写出优秀的代码。