Java多线程并发复习初步整理

前面对java.util的容器类的使用和源码实现做了个初步小结,但都是在不考虑并发的情况下。在这个多核时代,能够充分利用处理器资源处理大量的请求已是大势所趋,多线程并发处理是主流。随着请求量增加,并发开发也成了各大公司面试必考的方面。从本篇开始,本人对并发复习的一些内容作整理和小结。这篇主要是对Java并发开发相关的内容做一个粗略的梳理,更详细和和着重复习的锁机制等要点会后续整理出来。不像之前介绍的容器类,多线程并发开发从底层到上层涉及面很广,能够用好用活不是件容易事儿,可以说并发“水很深”,描述得不好的不对的欢迎拍砖,也希望能够和大家有更多交流。

多线程并发

(截自《Java Concurrency In Practice》原版封面)

并发的概念实际上是随着操作系统的发展而出现的,它的出现是有一定的需求背景的。并发解决了如下几个需求:计算资源的充分利用、公平、便捷性。公平性主要是针对操作系统的多用户,便捷性是指程序不用太去关心操作系统的进程调度。我们目前更能直接接触到到的是第一点,资源的充分利用。实际来想一想,对于一个多核的处理器来讲,如果我们采用了多线程并发开发,那么每一个任务可以分配到不同的处理器核心上去做完成。那么对于一个单核处理器呢?其实,计算机完成每一个任务不只是需要考虑处理器的,还有IO等,这些会造成线程的阻塞,使处理器空闲不能得到充分利用,而这正是大多情况下并发存在的意义。对于和UI密切相关的开发,高响应度也是一个并发带来的好处。

补充一句,并发也不只是有好的一面,任务间的切换也是相当有成本的。线程也不是越多越好,这也是会消耗资源的。更重要的,线程安全是一个很值得注意的问题。

看下并发在Java上的发展史。Java早在JDK1.0时代,就有了java.lang.Thread和java.lang.Runnable这两个东西。很多国内讲Java的教材都会写到这。Runnable接口很简单,就一个void返回类型的run()方法。而Thread则是Runnable的一个具体实现。虽然Thread在实际的Java应用开发中,直接用到会比较少,但它是Java并发的一个基础。如果你打开Thread的源码,可以看到和java.lang.Object相似,Thread的实现中很多重要方法带有native关键字,这就是需要JVM和底层操作系统打交道的地方。通常,一个最简单的开启新线程的方法,就是继承Thread,然后实例化并调用start()方法。或者把任务用Runnable对象分离开,传参给Thread。此外,对于线程同步,synchronized这个大家都熟悉的关键字也早早地就存在在Java平台中。

随着多核概念的流行,Java也顺应时代的潮流,并发开发全面兴起了。在JavaSE5中,JUC(java.util.concurrent)包出现了,这无疑引领Java彻底进入并发时代。在java.util.concurrent包及其子包中,有了很多好玩的新东西:

  • 执行器的概念和线程池的实现。从Executor接口开始,到ExecutorService,再到很多基于ThreadPoolExecutor实现的具体执行器。执行器实际上是采用了一种叫做命令模式的设计,将任务Runnable和具体执行线程相分离,并给出了生命周期等管理方法,一般只要execute一个Runnable任务即可。未能及时执行的任务放在一个队列中等待执行,这又是一种Producer & Customer模式。线程池的实现,则可以更方便地管理执行的线程,如重用线程,并限定线程所占资源上界,比起直接Thread的简陋用法更有实用更方便更有效率。
  • Callable、Future。前者使得提交线程有返回结果变成可能(Runnable的run()是void的),后者则对任务的生命周期控制更为直接。和CompletionService结合起来用更灵活。
  • 原子类。原子类的很多操作能够保证原子性,不保留中间状态,可以高效解决很多线程安全问题。需要注意的是,虽然java.util.concurrent.atomic包中有AtomicInteger、AtomicLong和AtomicReference等,但却没有AtomicDouble和AtomicFloat,这个需要自己实现。Sun的JDK原子类的操作很多是基于sun.misc.Unsafe的(此类并未随Jdk源码一起开放)。
  • 锁机制。java.util.concurrent除了atomic子包外,还有一个locks的子包。除了Java平台固有的synchronized对象锁机制,JavaSE5中的并发包用Java代码重新实现了一套锁机制,而这些很多都是基于前面提到的Unsafe类的原子操作。AbstractQueuedSynchronizer是这个体系中重要的抽象类。在JUC锁的实现体系中有和synchronized内在锁对应的Lock实现以及和监视器模型中wait()和notify()/notifyAll()对应的Condition类及其awati()和signal()/signalAll()。至于后者的特点,后续文章详谈。
  • 并发容器类和其它工具类。文章开头就提到了前面的容器没有在并发角度上做太多考虑,那么在并发情况下,JUC给我们带来的好东西就是更加实用的CopyOnWriteArrayList、ConcurrentHashMap以及BlockingQueue的各类实现。除此之外,CountDownLatch、CyclicBarrier、Semaphore、Exchanger都是很实用的并发工具类。

JavaSE5的java.util.concurrent真得是给我们带来了很多并发的新工具,在并发相关的各个方面都给出了很好的实现,和Java体系自身的线程和锁机制相比可以说是用Java代码又建立了一套新的并发类体系,这和前面提Java内建的数组和用Java代码实现的容器类有些类似。但并发这里和容器类体系不同的一点是,多线程并发离不开底层的支持,从上层的应用开发理念到底层的实现支持,Java并发框架的战线更长更复杂。不管怎样,JavaSE5给我们带来了并发时代的见面礼非常丰厚,而这些和一个牛人是分不开的,他就是Doug Lea,他参与了并发框架的大部分开发。

JavaSE5只是Java并发开发的一个开始,Java后续版本中也在不断探索更高效的并发模型和开发工具类实现。JavaSE6在锁机制实现上的改进,在Java7中ForkJoin的引入,都是Java并发的新发展。

前文也一直再提,并发开发内容的“水很深”。比如要说Java并发的内存模型,就可以聊好久。本博客后续文章主要是基于个人复习的整理,在日常开发的基础上,尝试深入理解一些东西。主要会考虑整理以下内容:

  • 线程和并发概念相关的内容,线程安全等
  • 原子操作和锁机制
  • 线程池和执行器
  • 容器类和并发工具类
  • 性能考虑和并发思想
  • ForkJoin探索

这一篇就先总述到这里,后续会在一些具体的点上着重整理。

此条目发表在 Java, Java语言, 并发 分类目录,贴了 , , , , , 标签。将固定链接加入收藏夹。

Java多线程并发复习初步整理》有 3 条评论

  1. 围观群众 说:

    很棒

  2. 雅琳 说:

    好东东,谢谢博主

  3. 世界与我无关 说:

    谢谢博主. 非常好的并发文章.

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>