代码改变世界

Java多线程基础

2014-01-13 11:43  hduhans  阅读(2644)  评论(0编辑  收藏  举报

  在一个程序中,能够独立运行的程序片段叫做“线程”,利用它进行编写的程序叫做多线程处理程序。通常情况下,多线程程序能够更好低利用计算机资源,提高程序执行的效率。多线程编程具有重要的意义,每个程序猿都应该掌握。

一、线程生命周期

1、新建状态:用Thread的new语句创建了线程对象,此时对象只在对内存中分配了内存。

2、就绪状态:当新建状态下的线程对象调用了start()方法后,该线程就进入了就绪状态,处于这个状态的线程位于可运行池中,等待获得CPU使用权。

3、运行状态:正在被CPU执行的线程状态。

4、阻塞状态:当线程因为某种原因处于阻塞状态时,JVM不会给线程分配CPU,直到线程重新进入就绪状态,才有机会被CPU运行。

   阻塞分为三种状态:

   1) 位于对象等待池中的阻塞状态:当线程运行获取对象锁后,执行了Object.wait()方法,JVM就会把该线程加入对象的等待池中,这种状态必须等待其他线程调用同个对象的notify()或notifyAll()方法时才有可能激活为就绪状态;

   2) 位于对象锁中的阻塞状态:当线程运行时,试图获取某个对象的同步锁时,如果该对象的同步锁已被其他线程占用,则JVM就会把当前线程放入对象的锁池中,当对象的同步锁被释放后,JVM就会根据一定的调度算法,将处于对象锁中阻塞状态的某个线程激活为就绪状态;

   3) 其他阻塞状态:当线程执行了sleep()方法,或者调用了其他线程的join()方法,或发出了I/O请求时,线程就会进入这个状态。

二、Java创建多线程的几种方式

  Java通过线程类Thread来创建多线程,每个线程是Thread类或其子类的实例对象,每个对象描述了一个单独的线程。Java中创建一个线程,有两种实现方法,详细介绍如下。

1、通过继承Thread类创建线程了解即可,不常用

   通过这种方式创建的线程之间是彼此相互独立的,各自有用自己的资源,互不干扰。

   假设一个影院有三个售票口,分别用于向儿童、成人和老人售票。影院为每个窗口放有100张电影票,分别是儿童票、成人票和老人票。三个窗口需要同时卖票,而现在只有一个售票员,这个售票员就相当于一个CPU,三个窗口就相当于三个线程。创建代码如下:

 1 /**
 2  * 各个线程之间执行是相互独立,互不干扰的 继承Thread类的线程不能共享资源
 3  */
 4 public class ThreadCreate {
 5     public static void main(String[] args) {
 6         MyThread m1 = new MyThread("窗口1");
 7         MyThread m2 = new MyThread("窗口2");
 8         MyThread m3 = new MyThread("窗口3");
 9         m1.start();
10         m2.start();
11         m3.start();
12     }
13 }
14 
15 class MyThread extends Thread {
16     private int ticket = 1;
17     MyThread(String name) {
18         super(name); // 调用父类带参数的构造方法
19     }
20     public void run() {
21         while (ticket <= 100) { // 每个线程都拥有100张票,各自卖各自得票
22             String threadName = Thread.currentThread().getName();
23             System.out.println("【" + threadName + "】售出第【" + ticket++ + "】张票");
24         }
25     }
26 }
View Code

   这种方式创建多线程的缺点:① Java是单继承的,继承Thead类后不能继承其他类,可能不满足开发需求;② 多线程之间无法共享资源

2、通过实现Runnable接口创建线程推荐使用

   通过继承Thread类创建的多线程可以满足非协同工作的多线程需求,但当要求各个线程之间需要处理共享资源时,只能通过实现Runnable接口的方式。

   假设上述影院的三个售票窗口销售的是同一种票,一共100一张票,每个窗口都可以卖票。此时创建代码如下:

 1 /** 
 2  * 本例中各个线程是共享100张票的资源的
 3  * 
 4  */
 5 public class RunnableCreate {
 6     public static void main(String[] args) {
 7         MyRunnable m =new MyRunnable();
 8         Thread t1=new Thread(m,"窗口1");
 9         Thread t2=new Thread(m,"窗口2");
10         Thread t3=new Thread(m,"窗口3");
11         t1.start();
12         t2.start();
13         t3.start();
14     }
15 }
16 
17 class MyRunnable implements Runnable{
18     private int ticket=1;
19     public void run(){
20         while(ticket<=100){  //几个线程共同卖100张票
21             String threadName = Thread.currentThread().getName();
22             System.out.println("【" + threadName + "】售出第【" + ticket++ + "】张票");
23         }
24         
25     }
26 }
View Code

   可以看出,这种方式创建多线程的优点:① 避免了Java但继承带来的局限性;② 多线程之间可以共享资源,相互协作

3、通过FutureTask类创建带返回值的线程需要获得线程返回值时使用

   FutureTask是一种可以取消的异步的计算任务。它的计算是通过Callable实现的,它等价于可以携带返回值的Runnable

   FutureTask是为了弥补Thread的不足而设计的,它可以让程序员准确地知道线程什么时候执行完成并获得到线程执行完成后返回的结果(如果有需要)。

   假设某个计算有两个步骤,综合计算结果为两个步骤结果的总和,可以将耗时较长的计算步骤通过FutureTask创建的线程来执行,等它计算结束,然后计算综合结果。创建代码如下:

 1 import java.util.Random;
 2 import java.util.concurrent.Callable;
 3 import java.util.concurrent.ExecutionException;
 4 import java.util.concurrent.FutureTask;
 5 
 6 class MyCallable implements Callable<Integer> {
 7     @Override
 8     public Integer call() throws Exception {
 9         Integer result = new Integer((new Random()).nextInt(10000));
10         Thread.sleep(2000);
11         return result;
12     }
13 }
14 
15 public class FutureTaskCreate {
16     public static void main(String[] args) throws InterruptedException, ExecutionException {
17         // 计算方法1的结果
18         FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
19         new Thread(futureTask).start();
20         // 计算方法2的结果
21         Integer result1 = new Integer((new Random()).nextInt(10000));
22         System.out.println("步骤1结果已经计算完毕,结果为:"+result1.intValue());
23         while (!futureTask.isDone()) {
24             try {
25                 Thread.sleep(500);
26                 System.out.println("步骤2的结果还在计算中,请稍等....");
27             } catch (InterruptedException e) {
28                 e.printStackTrace();
29             }
30         }
31         Integer result2 = futureTask.get();
32         System.out.println("步骤2结果已经计算完毕,结果为:"+result2.intValue());
33         System.out.println("综合计算结果为:" + (result1.intValue() + result2.intValue()));
34     }
35 }
View Code 

   通常来说,FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果

4、通过TimerTask类创建计划任务类线程

   Timer和TimerTask是用来创建定时任务,定时任务实质就是一个线程。TimerTask继承了Runnable接口,通过Timer启动计划任务入口有schedule和scheduleAtFixedRate两种方法。

   一个通过TimerTask来创建计划任务的示例代码如下:

 1 import java.util.Timer;
 2 import java.util.TimerTask;
 3 
 4 class MyTimerTask extends TimerTask{
 5     @Override
 6     public void run() {
 7         System.out.println("Timer子线程已经成功启动...");
 8     }
 9 }
10 
11 public class TimerTaskCreate {
12     public static void main(String[] args) {
13         Timer timer = new Timer();
14         //timer线程计划在1秒后启动
15         timer.schedule(new MyTimerTask(), 1000);
16     }
17 }
View Code

   更多详细关于Timer和TimerTask的详细介绍可参考:http://www.cnblogs.com/hanganglin/p/3526240.html

三、synchronized

    Java语言的关键字,用它来修饰一个方法或一个代码块的时候,能够保证在同一个时刻最多只有一个线程执行该段代码,可保证修饰的代码在执行过程中不会被其他线程干扰,即原子性

1、synchronized可修饰的对象

   1) synchronized {普通方法}:同一时间只能有一个线程访问同一个对象的该方法。缺点:同步整个方法效率不高。 synchronized void method() { ... }相当于void method( synchronized(this) { ... } )

   2) synchronized {代码块}:对代码块执行线程同步,效率要高于对整个函数执行同步,推荐使用这种方法。

   3) synchronized {static方法}:加锁的对象是类,同一时间,该类的所有对象中的synchronized static方法只能有一个线程访问。 class Foo { public synchronized static fun(){...}}等价于在class Foo { public static fun(){ synchronized(Foo.class){ ... } }}

class Foo {
    public synchronized static void print(String str) {
        for(int i=0;i<10;i++){
            System.out.print(str+" ");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println();
    }
}

public class SyncTest {
    public static void main(String[] args) {
        //三个Foo的对象foo1,foo2和foo3
        //如果print方法不加synchronized,不加static,则四个线程同时执行交替打印
        //如果print方法加synchronized,不加static,则同步同一个对象的print方法,即thread1-1和线程thread1-2不能同时执行,程序结果为thread1-1和thread2、thread3交替打印数据,等thread1-1执行结束,thread1-2开始打印
        //如果print方法加synchronized,加static,则对类Foo加锁,四个方法均同步执行,执行结果就是四个线程先后执行,上一个线程完全执行结束后下一个线程才开始执行
        final Foo foo1 = new Foo();
        final Foo foo2 = new Foo();
        final Foo foo3 = new Foo();
        new Thread(new Runnable() {public void run() {foo1.print("A1");}},"thread1-1").start();
        new Thread(new Runnable() {public void run() {foo1.print("A2");}},"thread1-2").start();
        new Thread(new Runnable() {public void run() {foo2.print("B");}},"thread2").start();
        new Thread(new Runnable() {public void run() {foo3.print("C");}},"thread3").start();
    }
}
View Code

   4) synchronized {run方法}:此时为同步普通方法的特殊情况,由于在线程的整个生命期内run方法一直在运行,因此同一个Runnable对象的多个线程只能串行运行。 

class MyThread implements Runnable {
    @Override
    public synchronized void run() {
        for(int i=0;i<10;i++){
            System.out.print(i+" ");
        }
        System.out.println();
    }
}

public class SyncTest {
    public static void main(String[] args) {
        //如果MyThread的run方法不加synchronized的话,则四个线程几乎同时执行,交替混乱的打印数字
        //如果MyThread的run方法添加synchronized同步,则四个线程同时只能有一个获得t的对象锁,四个线程交替执行,最后打印结果为4个0...9
        MyThread t=new MyThread ();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
    }
}
View Code

2、使用synchronized应重点理解以下细节:

   1) 当多个并发线程访问同一个对象的同步代码块时,一段时间内只能有一个线程得到执行,其他线程必须等待当前线程执行完代码块后再执行代码;

   2) 当一个线程访问一个对象的同步代码块时,其他线程可以访问该对象的中的非同步代码块;

   3) 当一个线程访问一个对象的同步代码块时,其他线程对该对象中的所有同步代码块均不能访问

   详细可参考如下例子,当fun2不加synchronized修饰时,线程tb可以在ta访问对象方法fun1的同时访问该对象的fun2方法。当fun2加上synchronized修饰后,线程tb必须等待ta执行完对象的fun1方法后才能执行该对象的fun2方法。

public class SyncTest {
    public synchronized void fun1() {
        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    //方法fun2增加synchronized时,当fun1正被某个线程访问时,fun2不能被其他线程访问
    public synchronized void fun2() {
        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        final SyncTest t = new SyncTest();
        Thread ta = new Thread(new Runnable() {
            public void run() {
                t.fun1();
            }
        },"Ta");
        Thread tb = new Thread(new Runnable() {
            public void run() {
                t.fun2();
            }
        },"Tb");
        ta.start();
        tb.start();
    }
}
View Code
四、ThreadLocal

   ThreadLocal主要用来更方便地访问线程内部变量,提供了保持对象的方法和避免参数传递的方便的对象访问方式。查阅了网上很多资料,大都观点说ThreadLocal是用来解决多线程共享对象的访问问题的,对此,本人完全不赞同,这压根就是两码事。当线程调用ThreadLocal.set()方法时,具体的值是保存在线程内部的ThreadLocal.ThreadLocalMap对象中,也就是说,每个线程设值的变量都只限于本线程访问,对于其他线程是隔离的,这与线程同步完全是两码事。

   假设三个打印机进程共享一台打印机资源,通常我们使用同步方法(如synchronized、Lock等)来对打印机进行同步操作,这是为了解决多线程下的安全问题。使用ThreadLocal,相当于增加了两台打印机资源,给每个打印进程分配了一台打印机。

   因此,ThreadLocal不是用来解决对象共享访问问题的也不是为线程提供共享变量的副本,而仅仅是为线程隔离对象

   此外,线程的数据是保存在线程内部的变量中的,而非保存在ThreadLocal对象中,ThreadLocal的get与set方法可以直接操作线程Thread内部ThreadLocal.ThreadLocalMap对象。在ThreadLocal.ThreadLocalMap中保存了一个Entry数组,通过ThreadLocal对象作为数组下标操作数据。

   在ThreadLocal.ThreadLocalMap中,使用了弱引用WeakReference,避免了内存泄露。

   多线程共享一个数据库连接会引发很多问题,我们可以为每个线程单独建立一个连接资源。可以结合单例模式与ThreadLocal创建“线程内部的单例模式”,每个线程都拥有一个实例。 

 1 public class ConnectionManager {
 2     private static ThreadLocal<Connection> maps = new ThreadLocal<Connection>();
 3     public Connection getInstance(){
 4         Connection conn = maps.get();
 5         if(conn == null) {
 6             try {
 7                 conn = DriverManager.getConnection("");//获取Connection对象
 8                 maps.set(conn);
 9             } catch (SQLException e) {
10                 e.printStackTrace();
11             }
12         }
13         return conn;
14     }
15 }
View Code
五、线程其他常用操作方法

1、Object.wait()Object.notify()Object.notifyAll()

   Object.wait是指线程在获取对象锁后,由于某些条件的不成立,主动释放对象锁,同时本线程进入对象等待池中处于阻塞状态。在synchronized同步块中,一旦线程捕获某个对象的同步锁,系统就很难控制线程,必须等待线程主动释放对象锁,这时候,在同步块内,使用Object.wait()可以使线程在进入synchronized同步块后主动释放对象锁。因此Object.wait()、Object.notify()和Object.notifyAll()方法必须在synchronized同步块内使用,否则会抛出IllegalMonitorStateException异常。

   1) Object.wait() :线程调用此方法后,只有当其他线程调用同个对象的notify()或notifyAll()方法后,才可能激活为就绪状态;

   2) Object.wait(long timeout) :线程调用此方法后,当其他线程调用同个对象的notify()或notifyAll()方法,或者超过时间timeout,线程都可能激活为就绪状态。

   当其他线程调用Object.notify()后,JVM会根据调度策略调取一个对象等待池中的线程,将其从阻塞状态激活为就绪状态,当此线程再次获得对象锁和CPU后,就可以进入执行状态。

   方法Object.notify()和方法Object.notifyAll()用于将处于wait等待状态的线程激活为就绪状态,notify()是根据调度策略激活某一个线程,notifyAll()是将所有处于等待线程池中的线程全部激活为就绪状态,但是激活后就绪状态的线程要想重新执行,必须再次获得对象锁。 

2、Thread.sleep(long millis)Thread.yield()

   这两个方法都会让当前正在执行的线程处于暂时停止执行的状态,交出CPU的使用权一段时间。与Object.wait()方法不同的是,Thread.sleep(long millis)和Thread.yield()在暂停线程的同时不会释放已获得的对象锁,而Object.wait()会暂停线程并且释放对象锁。

   两者的区别如下:

   1) Thread.sleep方法必须带一个时间参数,单位毫秒,当线程执行sleep后,在指定时间内,将转为阻塞状态;Thread.yield方法不带参数,当线程执行yield后,线程将进入就绪状态。

   2) Thread.sleep会抛出InterruptedException异常,而Thread.yield方法不会抛出异常。

   3) sleep()方法比yield()方法具有更好的移植性。??

   4) sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低线程一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。 ??

   实际上,yield()方法对应了如下操作: 先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程。yield()只是提前结束当前线程占用CPU的时间,线程转为就绪状态,等待下一个时间片再继续获得CPU并执行。

3、Thread.join()

   Thread.join()可以将多线程的异步变为同步,在父线程调用子线程的join方法后,必须等待子线程执行结束,父线程才会继续执行下去。Thread.join()方法会抛出InterruptedException异常。

class MyThread implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++) {
            System.out.print("A");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class SyncTest {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyThread());
        thread1.start();
        
        //如果此处不加thread1.join(),则主线程会与线程MyThread异步执行,由于线程MyThread每次输出睡眠500毫秒,程序输出结果为BB...BBAA...AA
        //如果此处加上thread1.join(),则主线程必须等待MyThread执行结束才会继续往下执行,程序输出结果为AA...AABB...BB
        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        for(int i=0;i<10;i++){
            System.out.print("B");
        }
    }
}
View Code

4、volatile关键字

   volatile是synchronized的一种弱实现,它可以保证变量的可见性,而不能保证程序执行的原子性。JVM运行多线程时,在主内存中保存着共享变量,每个线程运行时有一个自己的栈,用来保存从本线程运行需要的变量。当线程访问一个变量值的时候,首先通过对象的引用找到在主内存的地址,然后把变量的值拷贝到本线程的栈中,建立一个变量的副本。在线程对该变量计算的过程中,该变量副本和主内存的原始变量就没有任何关系了,当线程结算结束时,再将变量副本写回到主内存中对象变量的地址中,更新内存中的共享变量,详细的交互过程如下图所示。

  使用volatile修饰的变量,JVM只能保证从主内存加载到线程工作栈中的值是最新的,但使用过程不能完全保证线程对该变量同步的情况,因此,建议少使用volatile,对需要同步的地方使用synchronized。

5Thread.start()Thread.run() 

   启动线程应该使用Thread.start(),Thread.run()只是调用Runnable中的run方法,并没有启动线程,此时整个程序还是只有一个线程, 顺序执行

6、Thread.setDaemon()设置守护线程

   Java中有两类线程,分别是用户线程(User Thread)和守护线程(Daemon Thread)。守护线程是指在程序运行时后台提供一种通用服务的线程,如垃圾回收线程就是一个守护线程,当所有的非守护线程结束时,程序也就终止了,同时会结束所有的守护进程。用户线程和守护线程唯一的区别就是,当程序中只剩守护线程时,程序就会结束,而只要程序中还存在一个非守护线程,程序就不会终止。

   1) 普通线程转换为守护线程。Thread.SetDaemon(true)可以将普通线程转换为守护线程,但是设置必须在Thread.start()之前,否则会报IllegalThreadStateException异常。

   2) Daemon线程产生的子线程也是Daemon的。

   3) 守护线程应该永远不去访问固有资源,如文件和数据库等,因为他可能随时会中断。

import java.util.concurrent.TimeUnit;

public class DaemonsDontRunFinally {
    public static void main(String[] args) {
        Thread t = new Thread(new ADaemon());
        //设置为守护进程,则主线程结束守护进程就结束了,不会执行ADaemon中的finally输出语句
        t.setDaemon(true);
        t.start();
    }
}

class ADaemon implements Runnable {
    public void run() {
        try {
            System.out.println("start ADaemon...");
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            System.out.println("Exiting via InterruptedException");
        } finally {
            System.out.println("This shoud be always run ?");
        }
    }
}
View Code

7、Thread.interrput()中断线程(参考:http://jiangzhengjun.iteye.com/blog/652269)

   调用线程的Thread.interrupt方法中断线程时,JVM将会将对应线程内的中断状态位设置为true,可以在线程执行的方法中调用Thread.interrupted()或Thread.currentThread().isInterrupted()来检测中断位是否为true,至于线程下一步是死亡还是继续执行完全取决于程序本身,这一点与强制结束线程的已废弃的方法stop不同。

   当其他线程通过Thread.interrupt请求中断当前线程时,当前线程可能正处于非阻塞状态、阻塞状态或请求锁临时状态,下面来分析各种状态下如何来中断当前线程。

   1) 被请求中断时,当前线程处于非阻塞状态。

   此时可以在程序中调用方法获取线程内中断状态位的值,并根据该值自由决定是否结束当前线程。获取中断位值建议有两种方法:①Thread.currentThread().isInterrupted()方法,返回状态值;②Thread.interrupted()静态方法,返回状态值,并将中断位状态值重置为false。也就是说,此时若调用两次Thread.interrupted()方法,第二次方法返回值为false。因此建议使用方法Thread.currentThread().isInterrupted()来获取中断位状态值,常见线程循环时的中断方式如下所示:

1 while(!Thread.currentThread().isInterrupted() && more work to do){  
2     do more work  
3 }  

   一个使用Thread.interrupt中断非阻塞状态线程的例子如下所示:

 1 public class ThreadInterruptSample implements Runnable{
 2 
 3     public static void main(String[] args) throws InterruptedException {
 4         ThreadInterruptSample tis = new ThreadInterruptSample();
 5         System.out.println("程序开始执行....");
 6         Thread t = new Thread(tis);
 7         t.start();
 8         Thread.sleep(3000);
 9         System.out.println("主线程请求中断子线程....");
10         //请求中断线程t
11         t.interrupt();
12         Thread.sleep(3000);
13         System.out.println("主线程结束....");
14     }
15 
16     @Override
17     public void run() {
18         while(!Thread.currentThread().isInterrupted()){
19             long startTime = System.currentTimeMillis();
20             System.out.println("线程正在执行....");
21             //间隔一秒循环判断线程是否被请求中断
22             while(System.currentTimeMillis()-startTime<1000) {}
23         }
24         //这里可以执行线程中断前的一些处理工作
25         System.out.println("线程中断....");
26     }
27 }
View Code

   另一种实现方式-使用中断信号量(共享变量)中断非阻塞状态线程也是一种很受欢迎的方式,其实笔者认为,这种方式与使用Thread.interrupt来实现中断的内部机理是一样的。一个例子如下所示:

 1 public class ThreadInterruptByShareVariable implements Runnable {
 2     volatile boolean stop = false;
 3     
 4     public static void main(String[] args) throws InterruptedException {
 5         ThreadInterruptByShareVariable ttsv = new ThreadInterruptByShareVariable();
 6         Thread t = new Thread(ttsv);
 7         System.out.println("程序开始执行....");
 8         t.start();
 9         Thread.sleep(3000);
10         System.out.println("主线程请求中断子线程....");
11         //设置中断信号量
12         synchronized(ttsv){
13             ttsv.stop = true;
14         }
15         Thread.sleep(3000);
16         System.out.println("主线程结束....");
17     }
18 
19     @Override
20     public void run() {
21         while(!stop){
22             long startTime = System.currentTimeMillis();
23             System.out.println("线程正在执行....");
24             while(System.currentTimeMillis()-startTime<1000) {}
25         }
26         System.out.println("线程中断....");
27     }
28 }
View Code

   2) 被请求中断时,当前线程处于阻塞状态。 

   ① 对于由于调用Object.wait()、Thread.sleep()或Thread.join()等方式而处于阻塞状态的线程,被请求中断时会抛出异常InterruptException,使当前线程从阻塞状态激活进入异常代码块,便于结束。注意,在抛出异常InterruptException后,中断状态位会被重置为false,因此在使用Thread.currentThread().isInterrupted()检测循环是否中断的代码块内,若捕捉到此异常,必须将状态位重新设置为true,否则线程循环将永不停止

 1 public void run() {  
 2     while (!Thread.currentThread().isInterrupted()&& more work to do) {  
 3         try {  
 4             ...  
 5             sleep(delay);  
 6         } catch (InterruptedException e) {  
 7             Thread.currentThread().interrupt();;//重新设置中断标示  
 8         }  
 9     }  
10 } 
View Code

   一个使用Thread.interrupt中断阻塞状态线程的例子如下所示:

 1 public class ThreadInterruptSample implements Runnable{
 2     public static void main(String args[]) throws Exception {  
 3         ThreadInterruptSample tis = new ThreadInterruptSample();  
 4         Thread thread = new Thread(tis);
 5         System.out.println("程序开始执行....");  
 6         thread.start();  
 7         Thread.sleep(3000);  
 8         System.out.println("主线程请求中断子线程....");  
 9         //请求中断
10         thread.interrupt();
11         Thread.sleep(3000);  
12         System.out.println("主线程结束....");  
13     }  
14   
15     public void run() {  
16         while (!Thread.currentThread().isInterrupted()) {  
17             System.out.println("线程开始执行....");  
18             try {  
19                 /* 
20                  * 如果线程阻塞,将不会去检查中断信号量stop变量,所 以thread.interrupt() 
21                  * 会使阻塞线程从阻塞的地方抛出异常,让阻塞线程从阻塞状态逃离出来,并 
22                  * 进行异常块进行 相应的处理 
23                  */  
24                 Thread.sleep(1000);// 线程阻塞,如果线程收到中断操作信号将抛出异常  
25             } catch (InterruptedException e) {  
26                 System.out.println("线程接受到被请求的中断操作,正在响应是否中断...");  
27                 /* 
28                  * 如果线程在调用 Object.wait()方法,或者该类的 join() 、sleep()方法 
29                  * 过程中受阻,则其中断状态将被清除 
30                  */  
31                 System.out.println(Thread.currentThread().isInterrupted());// false  
32   
33                 //中不中断由自己决定,如果需要真真中断线程,则需要重新设置中断位,如果  
34                 //不需要,则不用调用  
35                 Thread.currentThread().interrupt();  
36             }  
37         }  
38         System.out.println("线程已经被中断.......");  
39     }  
40 }  
View Code

   ② 对由于请求I/O操作而处于阻塞状态的线程,被请求中断时,I/O通道会立即被关闭,并抛出异常ClosedByInterruptException,处理方式与上述一致。

   3) 被请求中断时,当前线程正处于获取锁的过程中,这时候线程是无法响应中断的,也就是说,当线程采用synchronized争夺锁资源而发生死锁时,使用Thread.interrupt是无法使线程中断的。补充一点,当使用Lock锁并且通过方法lockInterruptibly()设置响应中断锁时,线程可以被中断(详细参考博文http://www.cnblogs.com/hanganglin/articles/3577096.html)。

   结合上述说明,处理线程中断的方法应该同时适用于阻塞和非阻塞线程,一个常用方法架构如下所示

 1 public void run() {  
 2     try {  
 3         ...  
 4         /* 
 5          * 不管循环里是否调用过线程阻塞的方法如sleep、join、wait,这里还是需要加上 
 6          * !Thread.currentThread().isInterrupted()条件,虽然抛出异常后退出了循环,显 
 7          * 得用阻塞的情况下是多余的,但如果调用了阻塞方法但没有阻塞时,这样会更安全、更及时。 
 8          */  
 9         while (!Thread.currentThread().isInterrupted()&& more work to do) {  
10             do more work   
11         }  
12     } catch (InterruptedException e) {  
13         //线程在wait或sleep期间被中断了  
14     } finally {  
15         //线程结束前做一些清理工作  
16     }  
17 }
View Code

8、TimeUnit枚举工具

   TimeUnit是一个枚举,可以使用它来简化某些操作,如让线程休眠5分钟,可以写成:TimeUnit.MINUTES.sleep(5),相当于Thread.sleep(5*60*1000),时间会在TimeUnit内部自动转化。

9、Condition.await()Condition.signal()Condition.signalAll()

   同Object.wait()、Object.notify()和Object.notifyAll()对应功能一致,Object的方法用于synchronized同步块中,而Condition的方法用于ReentrantLock的lock()与unlock()之间。

   可用Reentrant.newCondition()来产生一个新的Condition。