理解线程概念是基础
简单的说,相对于操作系统而言,运行的程序我们称之为进程。而进程又可细分为多个线程,从这个角度来说,线程是操作系统能够调度的最小单位。服务于同一进程的不同线程,按照角色的不同,执行不同的任务,我们称为多线程。多线程由于共享内存资源而获得较高的运行效率,对于应用程序,尤其是企业级应用,性能指标尤为重要。
无间道II的一句经典台词“出来混,迟早要还的”被人津津乐道。多线程带来性能提高的同时,也引入复杂的问题。其中之一就是多线程的同步问题。试想一下,当多个线程同时读写同一份文件,一个线程正在写,还未写完,另一个线程就开始读,这样就引起了所谓的线程冲突。
掌握线程同步是关键
面对上述线程冲突的问题,一个习惯性的想法是,同一时间,要么读,要么写。当写线程(我们姑且命名为A)在执行时,读线程(我们命名为B)挂起,反之亦然。换句话说,就是把并行的两个操作,通过引入某种控制,变换成串行操作。我们把多线程通过特定的东西(如互斥量)来控制线程之间的执行顺序(同步)这种操作称之为线程同步。
在Java编程模型中,我们称所谓的互斥量为线程锁,它对应一个Java实例对象或者类对象。而同步这一过程使用关键字Synchronized表示。
千里之行始于足下:Synchronized语法详解
俗话说,工欲善其事必先利其器,在给出Java一些具体的编程实例前,我们先来看看Synchronized关键字的具体用法。
从使用方式上,Synchronized包含两种用法:Synchronized 方法和 Synchronized 块。前者在方法声明前加synchronized关键字,如:
用来控制对共享资源的访问。同一时间,仅允许一个线程操作该方法,其它线程排队。后者在代码块前加synchronized关键字,如:
和前者一样,同一时间,仅允许一个线程进入该代码块。两者的唯一区别是,Synchronized 方法对应的锁对象是this,而Synchronized 块对应的锁对象可以自由指定。
从作用域的角度,Synchronized也分为两种:实例对象和类对象。synchronized aMethod(){}是一个实例方法,这就意味着,对于同一个对象,同一时间,仅有可能被一个线程调用,其它线程排队。而对于与不同的对象,其它线程仍然能访问该方法。所不同的是,对于类对象而言,情况则完全不同。synchronized static aStaicMethod(){}是一个类方法,这就意味着,同一时间,该方法只能被一个线程调用,其它的线程没有任何机会。除非当前线程执行完操作或终止,释放线程锁。
此外,虽然继承是面向对象的Java语言特点之一,但是Synchronized关键字不具备继承性。也就是说,基类的方法synchronized aMethod(){} 在继承类中并不自动是synchronized aMethod(){},而是变成了aMethod(){}。继承类需要显式指定synchronized关键字。
Synchronized实战:生产者与消费者
线程的同步最经典的案例莫过于生产者与消费者问题。我们的例子将围绕它展开。生产者与消费者指的是两个线程共享一个公共的固定大小的缓冲区。其中一个是生产者线程,用于将“产品”放入缓冲区;另一个是消费者线程,用于从缓冲区取出“产品”。问题出现在缓冲区已满,生产者还想添加“产品”,其解决办法是让生产者此时休眠,待消费者从缓冲区取走一个或者多个“产品”再唤醒。同样地,当缓冲区已空,消费者还想取出“产品”,此时也可以让消费者休眠,待生产者放入一个或者多个数据时再唤醒它。
为简单起见,这里我们假设缓冲区的长度为1,这里的“产品”对应一个整数。该缓冲区提供读、写操作。对应的Buffer类如下:
wait()与notify()是Java线程同步机制最重要的两个方法。wait方法可以使在当前线程的对象等待,直到别的线程调用此对象的notify或notifyAll方法。
生产者线程类如下:
消费者线程类如下:
Main函数:
publicclass Sample {