编写多线程程序往往是为了提高资源的利用率,或者提高程序的运行效率,或者更好地监控程序的运行过程等。多线程同步处理的目的是为了让多个线程协调地并发工作。对多线程进行同步处理可以通过同步方法和同步语句块实现。Java虚拟机是通过对资源(如内存)加锁的方式实现这两种同步方式。这种机制带来的另一个问题就是死锁问题(即程序的所有线程都处于阻塞态或等待态)。良好的程序设计应当设法避开这种死锁问题。
一、多线程同步的基本原理
如果在多个并发线程之间共用资源,则可能就需要进行同步处理。Java虚拟机通过给每个对象加锁的方式实现多线程的同步处理。这里的对象包括类对象和实例对象两种。一个类的静态成员域和静态成员方法隶属于类对象。一个类的非静态成员域和非静态成员方法是不隶属于类对象的,而隶属于类的实例对象。
Java虚拟机为每个对象配备一把锁和一个等候集。对象内部锁住的是一些同步方法和同步语句块。一个方法要成为同步方法只要给该方法加上修饰词synchronized就可以。同步语句块的定义格式如下:
Synchronized(引用类型的表达式)
语句块
其中,关键字synchronized是同步语句块的引导词;位于“()”内的表达式必须是引用类型的表达式,指向某个类对象或实例对象,即指定与该同步语句块相关连的对象;语句块则由一对“{}”及这对大括号所括起来的一系列语句组成。
由于同步处理机制,Java虚拟机在运行同步方法或同步语句块时在时间和空间上需要一些额外开销。虽然利用多线程可以提高资源的利用率,但是如果过于频繁地用同步方法或同步语句块,则会降低多线程的并行度,并可能因为Java虚拟机的额外开销过大而最终降低程序的运行效率。
二、多线程的同步问题
(1)死锁问题
资源短缺会造成程序的所有线程都陷入等待态或阻塞态。死锁问题常常指的是造成这种情况的问题:资源实际上并不短缺,但由于程序设计不合理而造成程序的所有线程都处于等待态或阻塞态。典型的情况是每个线程都占有若干个资源的同时在等待若干个资源,而等待的资源都被其他线程所控制,所以每个线程都处于阻塞态。
(2)多线程同步的粒度问题
Java虚拟机通过Java语言的多线程特性提高了Java程序的运行效率。在多个线程之间常常因为共享内存等而需要同步处理。同步处理常常会降低线程的并行度,即让有些线程无法并行而只能串行。因此,很多资料认为多线程同步的粒度越小越好,即建议尽可能地减少在同步方法与同步语句块中的代码量,从而缩短多个线程串行运行的时间。实际上,这样会不会提高程序的运行效率是一个值得讨论的问题。这些资料忽略了Java虚拟机为线程的同步处理所需要的额外开销,即如果频繁地进入和退出同步方法或同步语句块,也会降低程序的运行效率。良好的多线程程序设计应当做好多线程同步的粒度与同步处理的次数之间的平衡关系,从而真正提高程序的运行效率。