如何实现多线程并发?

1,456 阅读3分钟

由于实现多线程并发能够极大地提高程序的运行效率,因此如何有效地实现多线程并发便成为了一个程序员重点关注的问题。

Java 内存模型

在实现多线程并发之前首先需要了解 Java 内存模型(Java Memory Model), Java MM 的关键是线程的工作内存与主内存之间的关系,工作内存存储的是线程实际操作的数据,而主内存则是线程所操作数据的实际所在位置,因此工作内存中的数据是主内存中对应数据的一个副本。Java MM 的示意图如下图所示。

Java内存模型Java内存模型

所有线程在对数据操作之前都先从主内存中将相应数据缓存到工作内存中,这样可以降低主内存的读取频率,减小数据读取时延,当线程处理完数据后再将其刷新到主内存中,注意若不加同步操作,工作内存中的数据在处理后不一定会马上刷新到主内存,同步操作将在后面介绍。

线程的两种生成方法

Java 可以采用两种方法生成线程,一种是直接继承 Thread 类,一种是实现 Runnable 接口,这两种方法各有优缺点。

  • 直接继承 Thread 类实现起来较为简单,并且类的一个实例就是一个单独的线程,如果需求简单可以直接采用这种方法,但由于 Java 单继承的性质导致继承 Thread 类之后无法继承其他类。
  • 实现 Runnable 接口可以实现数据和代码分离,数据和代码可以由多线程共享,并且实现 Runnable 接口并不影响继承其他类,实际使用中一般采用此方法。
线程之间如何进行通信?

线程之间可以通过多种方式进行通信,但面临的问题都在于如何确保共享信息的及时刷新和同步访问。

  • volatile 修饰共享数据,需要注意的是 volatile 只是保证共享数据在处理后及时刷新到主内存,但并不保证操作的原子性。
  • 利用管道在线程之间传输共享数据,具体可见利用喷泉码进行可靠传输

另外介绍Java的两种比较实用的同步类,这两种同步类都是实现多个线程相互等待的功能,其中 CyclicBarrier,如其名,可以重复运行,而 CountDownLatch 则运行一次后便失效。

  • CyclicBarrier
  • CountDownLatch
多线程如何优雅的结束?

说到线程的结束就不得不提一下 Java 的 GC(Garbage Collection)策略。

引用 Stack Overflow 上的一个答案(From falstro)。

A running thread is considered a so called garbage collection root and is one of those things keeping stuff from being garbage collected. When the garbage collector determines whether your object is ‘reachable’ or not, it is always doing so using the set of garbage collector roots as reference points.

Consider this, why is your main thread not being garbage collected, no one is referencing that one either.

因此若线程没有安全结束如陷入阻塞或者死循环状态,即使该线程没有被引用,JVM也是不会主动清理此线程的,从而会造成内存泄漏。

若多个线程之间存在同步操作,则需合理使用 wait(), notify(), notifyall()以及合理安排各线程的结束顺序。

多线程并发调试工具

最后介绍一个实用工具:VisualVM,利用其ThreadDump功能可以大大提高多线程并发调试效率。
Java VisualVMJava VisualVM