由于Java支持多线程,具有并发功能,从而大大提高了计算机的处理能力。在各线程之间不存在共享资源的情况下,几个线程的执行顺序可以是随机的。但是,当两个或两个以上的线程需要共享同一资源时,线程之间的执行次序就需要协调,并且在某个线程占用这一资源时,其他线程只能等待。例如生产者和消费者的问题,只有当生产者生产出产品并将其放入货架时,消费者才能从货架上取走产品进行消费。当生产者没有生产出产品时消费者是没法消费的。同理,当生产者生产的产品堆满货架时应该暂停生产等待消费者消费。在程序设计中,可用两个线程分别代表这里的生产者和消费者,可将货架视为任一时刻只允许一个线程访问的临界资源。
在这个问题中,两个线程要共享货架这一临界资源,需要在某些时刻(货空/货满)协调它们的工作,即货空时消费者应等待,而货满时生产者应等待。为了不致于发生混乱,还可进一步规定:当生产者往货架上放货物时不允许消费者取货物,当消费者从货架上取货物时不允许生产者放货物。这种机制在操作系统中称为线程间的同步。在同步机制中,将那些访问临界资源的程序段称为临界区。
在Java系统中,临界区程序段是用关键字“synchronized”来标注,并通过一个称为监控器的系统软件来管理的。当执行被冠以synchronized的程序段即临界区程序时,监控器将这段程序(访问的临界资源)加锁,此时,称该线程占有临界资源,直到这段程序执行完,才释放锁。只有锁被释放后,其他线程才可以访问这些临界资源。用关键字synchronized定义临界区的语句形式是:
synchronized (expression) statement 其中,expression代表类的名字,它是可选项;statement可以是一个方法,也可以是一个语句或一个语句块,最常见的是一个方法。下面通过一个例子来说明线程的同步问题。
1 public class thread 2 { 3 public static void main(String [ ] args) 4 { 5 HoldInt h=new HoldInt( ); //h为监控器 6 ProduceInt p=new ProduceInt(h); 7 ConsumeInt c=new ConsumeInt(h); 8 p.start( ); 9 c.start( ); 10 } 11 } 12 class HoldInt 13 { 14 private int sharedInt; 15 private boolean writeAble=true; //writeAble==true表示生产者线程能生产新数据 16 public synchronized void set(int val) //临界区程序段,也称为同步方法 17 { 18 while(!writeAble) 19 { 20 //生产者线程不能生产新数据时进入等待 21 try {wait( );} 22 catch(InterruptedException e){} 23 } 24 //生产者被唤醒后继续执行下面的语句 25 writeAble=false; 26 sharedInt=val; 27 notify( ); 28 } 29 public synchronized int get( ) //同步方法 30 { 31 while(writeAble) 32 { 33 //消费者线程不能消费数据时进入等待状态 34 try 35 { 36 wait( ); 37 }catch(InterruptedException e){} 38 } 39 //消费者被唤醒后继续执行下面的语句 40 writeAble=true; 41 notify( ); 42 return sharedInt; 43 } 44 } 45 //ProduceInt 是生产者线程 46 class ProduceInt extends Thread { 47 private HoldInt hi; 48 public ProduceInt(HoldInt hiForm) 49 { 50 hi=hiForm; 51 } 52 public void run( ) 53 { 54 for(int i=1;i<=4;i++) 55 { 56 hi.set(i); 57 System.out.println("产生的新数据是: "+ i); 58 } 59 } 60 } 61 //ConsumeInt 是消费者线程 62 class ConsumeInt extends Thread { 63 private HoldInt hi; 64 public ConsumeInt(HoldInt hiForm) 65 { 66 hi=hiForm; 67 } 68 public void run( ) 69 { 70 for(int i=1;i<=4;i++) 71 { 72 int val=hi.get( ); 73 System.out.println("读到的数据是: "+ val); 74 } 75 } 76 }
在这个程序中,共享数据sharedInt的方法set( )和get( )的头部的修饰符synchronized 使HoldInt的每个对象都有一把锁。当ProduceInt对象调用set( )方法时,HoldInt对象就被锁定。当set( )方法中的数据成员writeAble值为true时,set( )方法就可以向数据成员sharedInt中写入一个值,而get( )方法不能从sharedInt上读出值。如果set( )方法中的writeAble的值为false,则调用set( )方法中的wait( )方法,把调用set( )方法的ProduceInt对象放到HoldInt对象的等待队列中,并将HoldInt对象的锁打开,使该对象的其他synchronized方法可被调用。这个ProduceInt对象将一直在等待队列中等待,直到被唤醒使它进入就绪状态,等待分配CPU。当ProduceInt对象再次进入运行状态时,HoldInt对象就被隐含地锁定,而set( )方法将继续执行while循环中wait( )方法后面的语句。在本例中wait( )方法后面无其他语句,因此将进入下一次循环,判断while条件。 ConsumeInt 对象调用get( )方法与ProduceInt对象调用set( )方法的情况类似,不再详述。
该程序的运行结果如下:
产生的新数据是:1
产生的新数据是:2
读到的数据是:1
产生的新数据是:3
读到的数据是:2
产生的新数据是:4
读到的数据是:3
读到的数据是:4