java多线程学习笔记——详细

一、线程类 

  

    1、新建状态(New):新创建了一个线程对象。
        2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
        3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
        4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
        (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
        (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
        (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者                    I/O处理完毕时,线程重新转入就绪状态。
        5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

  线程调度:

  1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
                Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
                        static int MAX_PRIORITY 
                                  线程可以具有的最高优先级,取值为10。 
                        static int MIN_PRIORITY 
                                  线程可以具有的最低优先级,取值为1。 
                        static int NORM_PRIORITY 
                                  分配给线程的默认优先级,取值为5。 
        Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
        每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
        线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
        JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
        2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
        3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
        4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
        5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
        6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
        注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。
        7、常见线程名词解释
        主线程:JVM调用程序mian()所产生的线程。
        当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。
        后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。
        前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。


  Java是通过Java.lang.Thread类来实现多线程的,第个Thread对象描述了一个单独的线程。要产生一个线程,有两种方法: 
    1、需要从Java.lang.Thread类继承一个新的线程类,重载它的run()方法; 
    2、通过Runnalbe接口实现一个从非线程类继承来类的多线程,重载Runnalbe接口的run()方法。运行一个新的线程,只需要调用它的start()方法即可。如:

/**=====================================================================
* 文件:ThreadDemo_01.java
* 描述:产生一个新的线程
* ======================================================================
*/
class ThreadDemo extends Thread{
  
  // 重载run函数
  public void run()
  {
     for (int count = 1,row = 1; row < 20; row++,count++)
     {
        for (int i = 0; i < count; i++)
        {
           System.out.print('*');
        }
        System.out.println();
     }
  }
}

class ThreadMain{
  public static void main(String argv[]){
    ThreadDemo th = new ThreadDemo();
    // 调用start()方法执行一个新的线程
    th.start();
  }
}

 

 

线程类的一些常用方法: 

  sleep(): 强迫一个线程睡眠N毫秒。 
  isAlive(): 判断一个线程是否存活。 
  join(): 等待线程终止。 
  activeCount(): 程序中活跃的线程数。 
  enumerate(): 枚举程序中的线程。 
    currentThread(): 得到当前线程。 
  isDaemon(): 一个线程是否为守护线程。 
  setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) 
  setName(): 为线程设置一个名称。 
  wait(): 强迫一个线程等待。 
  notify(): 通知一个线程继续运行。 
  setPriority(): 设置一个线程的优先级。 
二、等待一个线程的结束 
  有些时候我们需要等待一个线程终止后再运行我们的另一个线程,这时我们应该怎么办呢?请看下面的例子:

/**=====================================================================
* 文件:ThreadDemo_02.java
* 描述:等待一个线程的结束
* ======================================================================
*/
class ThreadDemo extends Thread{

  
  // 重载run函数
  public void run()
  {
     for (int count = 1,row = 1; row < 20; row++,count++)
     {
        for (int i = 0; i < count; i++)
        {
           System.out.print('*');
        }
        System.out.println();
     }
  }
}

class ThreadMain{
  public static void main(String argv[]){
    //产生两个同样的线程
    ThreadDemo th1 = new ThreadDemo();
    ThreadDemo th2 = new ThreadDemo();

   // 我们的目的是先运行第一个线程,再运行第二个线程
   th1.start();
   th2.start();
  }
}

 

这里我们的目标是要先运行第一个线程,等第一个线程终止后再运行第二个线程,而实际运行的结果是如何的呢?实际上我们运行的结果并不是两个我们想要的直角三角形,而是一些乱七八糟的*号行,有的长,有的短。为什么会这样呢?因为线程并没有按照我们的调用顺序来执行,而是产生了线程赛跑现象。实际上Java并不能按我们的调用顺序来执行线程,这也说明了线程是并行执行的单独代码。如果要想得到我们预期的结果,这里我们就需要判断第一个线程是否已经终止,如果已经终止,再来调用第二个线程。代码如下:

/**=====================================================================
* 文件:ThreadDemo_03.java
* 描述:等待一个线程的结束的两种方法
* ======================================================================
*/
class ThreadDemo extends Thread{

  
  // 重载run函数
  public void run()
  {
     for (int count = 1,row = 1; row < 20; row++,count++)
     {
        for (int i = 0; i < count; i++)
        {
           System.out.print('*');
        }
        System.out.println();
     }
  }
}

class ThreadMain{
  public static void main(String argv[]){
    ThreadMain test = new ThreadMain();
    test.Method1();
    // test.Method2();
  }

  // 第一种方法:不断查询第一个线程是否已经终止,如果没有,则让主线程睡眠一直到它终止为止
 // 即:while/isAlive/sleep
 public void Method1(){
    ThreadDemo th1 = new ThreadDemo();
    ThreadDemo th2 = new ThreadDemo();
    // 执行第一个线程
    th1.start();
    // 不断查询第一个线程的状态
    while(th1.isAlive()){
      try{
         Thread.sleep(100);
      }catch(InterruptedException e){
      }
    }
    //第一个线程终止,运行第二个线程
    th2.start();
  }
  
  // 第二种方法:join()
  public void Method2(){
    ThreadDemo th1 = new ThreadDemo();
    ThreadDemo th2 = new ThreadDemo();
    // 执行第一个线程
    th1.start();
    try{
      th1.join();
    }catch(InterruptedException e){
    }
    // 执行第二个线程
  th2.start();
}

 

三、线程的同步问题 
   有些时候,我们需要很多个线程共享一段代码,比如一个私有成员或一个类中的静态成员,但是由于线程赛跑的问题,所以我们得到的常常不是正确的输出结果,而相反常常是张冠李戴,与我们预期的结果大不一样。看下面的例子:

/**=============================================================================
 * 文件:ThreadDemo_04.java
 * 描述:多线程不同步的原因
 * =============================================================================
 */
// 共享一个静态数据对象
class ShareData{
 public static String szData = "";
}

class ThreadDemo extends Thread{
  
  private ShareData oShare;

  ThreadDemo(){
  }

  ThreadDemo(String szName,ShareData oShare){
    super(szName);
    this.oShare = oShare;
  }

  public void run(){
    // 为了更清楚地看到不正确的结果,这里放一个大的循环
  for (int i = 0; i < 50; i++){
       if (this.getName().equals("Thread1")){
         oShare.szData = "这是第 1 个线程";
         // 为了演示产生的问题,这里设置一次睡眠
     try{
           Thread.sleep((int)Math.random() * 100);
         catch(InterruptedException e){
         }
         // 输出结果
         System.out.println(this.getName() + ":" + oShare.szData);
       }else if (this.getName().equals("Thread2")){
         oShare.szData = "这是第 1 个线程";
         // 为了演示产生的问题,这里设置一次睡眠
     try{
           Thread.sleep((int)Math.random() * 100);
         catch(InterruptedException e){
         }
         // 输出结果
         System.out.println(this.getName() + ":" + oShare.szData);
       }
   }
}

class ThreadMain{
  public static void main(String argv[]){
    ShareData oShare = new ShareData();
    ThreadDemo th1 = new ThreadDemo("Thread1",oShare);
    ThreadDemo th2 = new ThreadDemo("Thread2",oShare);

    th1.start();
    th2.start();
  }
}

 

由于线程的赛跑问题,所以输出的结果往往是Thread1对应“这是第 2 个线程”,这样与我们要输出的结果是不同的。为了解决这种问题(错误),Java为我们提供了“锁”的机制来实现线程的同步。锁的机制要求每个线程在进入共享代码之前都要取得锁,否则不能进入,而退出共享代码之前则释放该锁,这样就防止了几个或多个线程竞争共享代码的情况,从而解决了线程的不同步的问题。可以这样说,在运行共享代码时则是最多只有一个线程进入,也就是和我们说的垄断。锁机制的实现方法,则是在共享代码之前加入synchronized段,把共享代码包含在synchronized段中。上述问题的解决方法为:

/**=============================================================================
 * 文件:ThreadDemo_05.java
 * 描述:多线程不同步的解决方法--锁
 * =============================================================================
 */
// 共享一个静态数据对象
class ShareData{
 public static String szData = "";
}

class ThreadDemo extends Thread{
  
  private ShareData oShare;

  ThreadDemo(){
  }

  ThreadDemo(String szName,ShareData oShare){
    super(szName);
    this.oShare = oShare;
  }

  public void run(){
    // 为了更清楚地看到不正确的结果,这里放一个大的循环
  for (int i = 0; i < 50; i++){
       if (this.getName().equals("Thread1")){
         // 锁定oShare共享对象
         synchronized (oShare){
           oShare.szData = "这是第 1 个线程";
           // 为了演示产生的问题,这里设置一次睡眠
       try{
             Thread.sleep((int)Math.random() * 100);
           catch(InterruptedException e){
           }
           // 输出结果
           System.out.println(this.getName() + ":" + oShare.szData);
         }
       }else if (this.getName().equals("Thread2")){
         // 锁定共享对象
         synchronized (oShare){
           oShare.szData = "这是第 1 个线程";
           // 为了演示产生的问题,这里设置一次睡眠
       try{
             Thread.sleep((int)Math.random() * 100);
           catch(InterruptedException e){
           }
           // 输出结果
           System.out.println(this.getName() + ":" + oShare.szData);
         }
       }
   }
}

class ThreadMain{
  public static void main(String argv[]){
    ShareData oShare = new ShareData();
    ThreadDemo th1 = new ThreadDemo("Thread1",oShare);
    ThreadDemo th2 = new ThreadDemo("Thread2",oShare);

    th1.start();
    th2.start();
  }
}

 

由于过多的synchronized段将会影响程序的运行效率,因此引入了同步方法,同步方法的实现则是将共享代码单独写在一个方法里,在方法前加上synchronized关键字即可。 

  在线程同步时的两个需要注意的问题: 
  1、无同步问题:即由于两个或多个线程在进入共享代码前,得到了不同的锁而都进入共享代码而造成。 
  2、死锁问题:即由于两个或多个线程都无法得到相应的锁而造成的两个线程都等待的现象。这种现象主要是因为相互嵌套的synchronized代码段而造成,因此,在程序中尽可能少用嵌套的synchronized代码段是防止线程死锁的好方法。

Java多线程学习笔记(二) 
四、Java的等待通知机制 
  在有些时候,我们需要在几个或多个线程中按照一定的秩序来共享一定的资源。例如生产者--消费者的关系,在这一对关系中实际情况总是先有生产者生产了产品后,消费者才有可能消费;又如在父--子关系中,总是先有父亲,然后才能有儿子。然而在没有引入等待通知机制前,我们得到的情况却常常是错误的。这里我引入《用线程获得强大的功能》一文中的生产者--消费者的例子:

/* ==================================================================================
 * 文件:ThreadDemo07.java
 * 描述:生产者--消费者
 * 注:其中的一些注释是我根据自己的理解加注的
 * ==================================================================================
 */

// 共享的数据对象
 class ShareData{
  private char c;
  
  public void setShareChar(char c){
   this.c = c;
  }
  
  public char getShareChar(){
   return this.c;
  }
 }
 
 // 生产者线程
 class Producer extends Thread{
  
  private ShareData s;
  
  Producer(ShareData s){
   this.s = s;
  }
  
  public void run(){
   for (char ch = 'A'; ch <= 'Z'; ch++){
    try{
     Thread.sleep((int)Math.random() * 4000);
    }catch(InterruptedException e){}
    
    // 生产
    s.setShareChar(ch);
    System.out.println(ch + " producer by producer.");
   }
  }
 }
 
 // 消费者线程
 class Consumer extends Thread{
  
  private ShareData s;
  
  Consumer(ShareData s){
   this.s = s;
  }
  
  public void run(){
   char ch;
   
   do{
    try{
     Thread.sleep((int)Math.random() * 4000);
    }catch(InterruptedException e){}
    // 消费
    ch = s.getShareChar();
    System.out.println(ch + " consumer by consumer.");
   }while(ch != 'Z');
  }
 }

class Test{
 public static void main(String argv[]){
  ShareData s = new ShareData();
  new Consumer(s).start();
  new Producer(s).start();
 }
}

 

在以上的程序中,模拟了生产者和消费者的关系,生产者在一个循环中不断生产了从A-Z的共享数据,而消费者则不断地消费生产者生产的A-Z的共享数据。我们开始已经说过,在这一对关系中,必须先有生产者生产,才能有消费者消费。但如果运行我们上面这个程序,结果却出现了在生产者没有生产之前,消费都就已经开始消费了或者是生产者生产了却未能被消费者消费这种反常现象。为了解决这一问题,引入了等待通知(wait/notify)机制如下: 
  1、在生产者没有生产之前,通知消费者等待;在生产者生产之后,马上通知消费者消费。 
  2、在消费者消费了之后,通知生产者已经消费完,需要生产。 
下面修改以上的例子(源自《用线程获得强大的功能》一文):

/* ==================================================================================
 * 文件:ThreadDemo08.java
 * 描述:生产者--消费者
 * 注:其中的一些注释是我根据自己的理解加注的
 * ==================================================================================
 */

class ShareData{
 
 private char c;
 // 通知变量
 private boolean writeable = true;

 // ------------------------------------------------------------------------- 
 // 需要注意的是:在调用wait()方法时,需要把它放到一个同步段里,否则将会出现
 // "java.lang.IllegalMonitorStateException: current thread not owner"的异常。
 // -------------------------------------------------------------------------
 public synchronized void setShareChar(char c){
  if (!writeable){
   try{
    // 未消费等待
    wait();
   }catch(InterruptedException e){}
  }
  
  this.c = c;
  // 标记已经生产
  writeable = false;
  // 通知消费者已经生产,可以消费
  notify();
 }
 
 public synchronized char getShareChar(){
  if (writeable){
   try{
    // 未生产等待
    wait();
   }catch(InterruptedException e){}  
  }
  // 标记已经消费
  writeable = true;
  // 通知需要生产
  notify();
  return this.c;
 }
}

// 生产者线程
class Producer extends Thread{
 
 private ShareData s;
 
 Producer(ShareData s){
  this.s = s;
 }
 
 public void run(){
  for (char ch = 'A'; ch <= 'Z'; ch++){
   try{
    Thread.sleep((int)Math.random() * 400);
   }catch(InterruptedException e){}
   
   s.setShareChar(ch);
   System.out.println(ch + " producer by producer.");
  }
 }
}

// 消费者线程
class Consumer extends Thread{
 
 private ShareData s;
 
 Consumer(ShareData s){
  this.s = s;
 }
 
 public void run(){
  char ch;
  
  do{
   try{
    Thread.sleep((int)Math.random() * 400);
   }catch(InterruptedException e){}
  
   ch = s.getShareChar();
   System.out.println(ch + " consumer by consumer.**");
  }while (ch != 'Z');
 }
}

class Test{
 public static void main(String argv[]){
  ShareData s = new ShareData();
  new Consumer(s).start();
  new Producer(s).start();
 }
}

 

在以上程序中,设置了一个通知变量,每次在生产者生产和消费者消费之前,都测试通知变量,检查是否可以生产或消费。最开始设置通知变量为true,表示还未生产,在这时候,消费者需要消费,于时修改了通知变量,调用notify()发出通知。这时由于生产者得到通知,生产出第一个产品,修改通知变量,向消费者发出通知。这时如果生产者想要继续生产,但因为检测到通知变量为false,得知消费者还没有生产,所以调用wait()进入等待状态。因此,最后的结果,是生产者每生产一个,就通知消费者消费一个;消费者每消费一个,就通知生产者生产一个,所以不会出现未生产就消费或生产过剩的情况。 

五、线程的中断 
  在很多时候,我们需要在一个线程中调控另一个线程,这时我们就要用到线程的中断。用最简单的话也许可以说它就相当于播放机中的暂停一样,当第一次按下暂停时,播放器停止播放,再一次按下暂停时,继续从刚才暂停的地方开始重新播放。而在Java中,这个暂停按钮就是Interrupt()方法。在第一次调用interrupt()方法时,线程中断;当再一次调用interrupt()方法时,线程继续运行直到终止。这里依然引用《用线程获得强大功能》一文中的程序片断,但为了更方便看到中断的过程,我在原程序的基础上作了些改进,程序如下:

/* ===================================================================================
 * 文件:ThreadDemo09.java
 * 描述:线程的中断
 * ===================================================================================
 */
class ThreadA extends Thread{
 
 private Thread thdOther;
 
 ThreadA(Thread thdOther){
  this.thdOther = thdOther;
 }
 
 public void run(){
  
  System.out.println(getName() + " 运行...");
  
  int sleepTime = (int)(Math.random() * 10000);
  System.out.println(getName() + " 睡眠 " + sleepTime
   + " 毫秒。");
  
  try{
   Thread.sleep(sleepTime);
  }catch(InterruptedException e){}
  
  System.out.println(getName() + " 觉醒,即将中断线程 B。");
  // 中断线程B,线程B暂停运行
  thdOther.interrupt();
 }
}

class ThreadB extends Thread{
 int count = 0;
 
 public void run(){
  
  System.out.println(getName() + " 运行...");
  
  while (!this.isInterrupted()){
   System.out.println(getName() + " 运行中 " + count++);
      
   try{  
    Thread.sleep(10);
   }catch(InterruptedException e){
    int sleepTime = (int)(Math.random() * 10000);
    System.out.println(getName() + " 睡眠" + sleepTime
     + " 毫秒。觉醒后立即运行直到终止。");
    
    try{
     Thread.sleep(sleepTime);
    }catch(InterruptedException m){}
    
    System.out.println(getName() + " 已经觉醒,运行终止...");
    // 重新设置标记,继续运行 
    this.interrupt();
   }
  }
  
  System.out.println(getName() + " 终止。");  
 }
}

class Test{
 public static void main(String argv[]){
  ThreadB thdb = new ThreadB();
  thdb.setName("ThreadB");
  
  ThreadA thda = new ThreadA(thdb);
  thda.setName("ThreadA");
  
  thdb.start();
  thda.start();
 }
}
  运行以上程序,你可以清楚地看到中断的过程。首先线程B开始运行,接着运行线程A,在线程A睡眠一段时间觉醒后,调用interrupt()方法中断线程B,此是可能B正在睡眠,觉醒后掏出一个InterruptedException异常,执行其中的语句,为了更清楚地看到线程的中断恢复,我在InterruptedException异常后增加了一次睡眠,当睡眠结束后,线程B调用自身的interrupt()方法恢复中断,这时测试isInterrupt()返回true,线程退出。
线程和进程(Threads and Processes)
第一个关键的系统级概念,究竟什么是线程或者说究竟什么是进程?她们其实就是操作系统内部的一种数据结构。
进程数据结构掌握着所有与内存相关的东西:全局地址空间、文件句柄等等诸如此类的东西。当一个进程放弃执行(准确的说是放弃占有CPU),而被操作系统交换到硬盘上,使别的进程有机会运行的时候,在那个进程里的所有数据也将被写到硬盘上,甚至包括整个系统的核心(core memory)。可以这么说,当你想到进程(process),就应该想到内存(memory) (进程 == 内存)。如上所述,切换进程的代价非常大,总有那么一大堆的内存要移来移去。你必须用秒这个单位来计量进程切换(上下文切换),对于用户来说秒意味着明显的等待和硬盘灯的狂闪(对于作者的我,就意味着IBM龙腾3代的烂掉,5555555)。言归正传,对于Java而言,JVM就几乎相当于一个进程(process),因为只有进程才能拥有堆内存(heap,也就是我们平时用new操作符,分出来的内存空间)。
那么线程是什么呢?你可以把它看成“一段代码的执行”---- 也就是一系列由JVM执行的二进制指令。这里面没有对象(Object)甚至没有方法(Method)的概念。指令执行的序列可以重叠,并且并行的执行。后面,我会更加详细的论述这个问题。但是请记住,线程是有序的指令,而不是方法(method)。
线程的数据结构,与进程相反,仅仅只包括执行这些指令的信息。它包含当前的运行上下文(context):如寄存器(register)的内容、当前指令的在运行引擎的指令流中的位置、保存方法(methods)本地参数和变量的运行时堆栈。如果发生线程切换,OS只需把寄存器的值压进栈,然后把线程包含的数据结构放到某个类是列表(LIST)的地方;把另一个线程的数据从列表中取出,并且用栈里的值重新设置寄存器。切换线程更加有效率,时间单位是毫秒。对于Java而言,一个线程可以看作是JVM的一个状态。
运行时堆栈(也就是前面说的存储本地变量和参数的地方)是线程数据结构一部分。这是因为多个线程,每一个都有自己的运行时堆栈,也就是说存储在这里面的数据是绝对线程安全(后面将会详细解释这个概念)的。因为可以肯定一个线程是无法修改另一个线程的系统级的数据结构的。也可以这么说一个不访问堆内存的(只读写堆栈内存)方法,是线程安全的(Thread Safe)。
线程安全和同步
线程安全,是指一个方法(method)可以在多线程的环境下安全的有效的访问进程级的数据(这些数据是与其他线程共享的)。事实上,线程安全是个很难达到的目标。
线程安全的核心概念就是同步,它保证多个线程:
同时开始执行,并行运行
不同时访问相同的对象实例
不同时执行同一段代码
我将会在后面的章节,一一细诉这些问题。但现在还是让我们来看看同步的一种经典的
实现方法——信号量。信号量是任何可以让两个线程为了同步它们的操作而相互通信的对象。Java也是通过信号量来实现线程间通信的。
不要被微软的文档所暗示的信号量仅仅是Dijksta提出的计数型信号量所迷惑。信号量其实包含任何可以用来同步的对象。
如果没有synchronized关键字,就无法用JAVA实现信号量,但是仅仅只依靠它也不足够。我将会在后面为大家演示一种用Java实现的信号量。
同步的代价很高哟!
       同步(或者说信号量,随你喜欢啦)的一个很让人头痛的问题就是代价。考虑一下,下面的代码:
Listing 1.2:
import java.util.*;
import java.text.NumberFormat;
class Synch
{
    private static long[ ] locking_time = new long[100];
    private static long[ ] not_locking_time = new long[100];
  private static final long ITERATIONS = 10000000;
    synchronized long locking     (long a, long b){return a + b;}
    long              not_locking (long a, long b){return a + b;}

      private void test( int id )
      {
       long start = System.currentTimeMillis(); 
       for(long i = ITERATIONS; --i >= 0 ;)
           {     locking(i,i);           }
       locking_time[id] = System.currentTimeMillis() - start;
       start = System.currentTimeMillis();
        for(long i = ITERATIONS; --i >= 0 ;)
        {     not_locking(i,i);           }
          not_locking_time[id] = System.currentTimeMillis() - start;
      }
       static void print_results( int id )
       {     NumberFormat compositor = NumberFormat.getInstance();
             compositor.setMaximumFractionDigits( 2 );
             double time_in_synchronization = locking_time[id] - not_locking_time[id];
        System.out.println( "Pass " + id + ": Time lost: "
                       + compositor.format( time_in_synchronization                         )
                      + " ms. "
                       + compositor.format( ((double)locking_time[id]/not_locking_time[id])*100.0 )
                        + "% increase"                       );
       }
    static public void main(String[ ] args) throws InterruptedException
   {
             final Synch tester = new Synch();
             tester.test(0); print_results(0);
             tester.test(1); print_results(1);
             tester.test(2); print_results(2);
             tester.test(3); print_results(3);
             tester.test(4); print_results(4);
             tester.test(5); print_results(5);
             tester.test(6); print_results(6);
           final Object start_gate = new Object();
              Thread t1 = new Thread()
              {     public void run()
                     {     try{ synchronized(start_gate) {     start_gate.wait(); } }
                            catch( InterruptedException e ){}
                             tester.test(7);
                     }
              };
              Thread t2 = new Thread()
              {     public void run()
                     {     try{ synchronized(start_gate) {     start_gate.wait(); } }
                            catch( InterruptedException e ){}
                             tester.test(8);
                     }
              };
              Thread.currentThread().setPriority( Thread.MIN_PRIORITY );
              t1.start();
              t2.start();
              synchronized(start_gate){ start_gate.notifyAll(); }
              t1.join();
              t2.join();
              print_results( 7 );
              print_results( 8 );
    }
}

 

运行以上程序,你可以清楚地看到中断的过程。首先线程B开始运行,接着运行线程A,在线程A睡眠一段时间觉醒后,调用interrupt()方法中断线程B,此是可能B正在睡眠,觉醒后掏出一个InterruptedException异常,执行其中的语句,为了更清楚地看到线程的中断恢复,我在InterruptedException异常后增加了一次睡眠,当睡眠结束后,线程B调用自身的interrupt()方法恢复中断,这时测试isInterrupt()返回true,线程退出。 

posted @ 2015-12-12 21:10  走在大牛的路上  阅读(280)  评论(0编辑  收藏  举报