Java高级特性系列--多线程
Posted coder为
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java高级特性系列--多线程相关的知识,希望对你有一定的参考价值。
转载自:http://www.cnblogs.com/skywang12345/p/3479202.html
进程:受操作系统管理的基本运行单元。360浏览器是一个进程,正在操作系统中运行的.exe都可以理解为一个进程。
线程:进程中独立运行的子任务就是线程。像QQ.exe运行的时候就有很多子任务在运行,比如聊天线程/好友视频线程/下载文件线程等。
多线程优点:
1,资源利用率更好
2,程序设计更简单
3,程序相应更快
多线程代价:
1,有时程序设计会更复杂
2,上下文切换开销大
3,增加资源消耗
一,多线程相关概念:
线程的5种状态:
1,新建状态(New):线程对象被创建之后,就进入了新建状态。Thread thread = new Thread();
2, 就绪状态(Runnable):可执行状态,线程对象被创建后,其他线程调用了该对象的start()方法,该线程就启动了。处于就绪状态,随时可能被CPU调度执行。
3,运行状态(Running):线程获取到CPU正在执行。线程只能从就绪状态转为运行状态,不能从其他状态进入运行状态。
4,阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃了CPU的使用权,暂时停止运行。阻塞分为三种情况:
(a)等待阻塞:通过调用线程的wait()方法,让线程等待某项工作的完成。
(b)同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),就会进入同步阻塞状态。
(c)其他阻塞:通过调用线程的sleep() or join() or 发生IO请求时,线程会进入阻塞状态。当sleep()超时,join()等待线程终止或超时,或者IO操作处理完成时,线程重新进入就绪状态。
5,死亡状态(Dead):线程完成了或者因异常退出了run()方法,该线程结束生命周期。
二,常用的实现多线程的两种方式
1,实现Runnable接口
2,继承Thread类
Thread是类,而Runnable是接口,Thread本身实现了Runnable接口。推荐使用Runnable接口,因为一,允许实现类去继承其他父类,可扩展性增加;二,可以实现资源共享,多个线程都是基于一个Runnable对象建立的,这样它们就会共享Runnable对象上的资源。
三,synchronized
在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。如果某个对象的同步锁被某个线程占用,那么其他线程就必须等待。
当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj)就获取了“obj这个对象”的同步锁。
不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。
我们将synchronized的基本规则总结为下面3条,并通过实例对它们进行说明。
第一条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第二条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
第三条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
四,锁
实例锁:锁在某个实例对象上。实例锁对应的就是synchronized关键字。
全局锁:该锁针对的是类,无论实例多少,线程都共享该锁。全局锁对应的是static synchronized,或者是锁在该类的class或者classloader对象上。
关于“实例锁”和“全局锁”有一个很形象的例子:
pulbic class Something { public synchronized void isSyncA(){} public synchronized void isSyncB(){} public static synchronized void cSyncA(){} public static synchronized void cSyncB(){} }
假设,Something有两个实例x和y。分析下面4组表达式获取的锁的情况。
(01) x.isSyncA()与x.isSyncB() 不可以被同时访问
(02) x.isSyncA()与y.isSyncA() 可以被同时访问
(03) x.cSyncA()与y.cSyncB() 不可以被同时访问
(04) x.isSyncA()与Something.cSyncA() 可以被同时访问
五,线程等待与唤醒
wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
Object类中关于等待/唤醒的API详细信息如下:
notify() -- 唤醒在此对象监视器上等待的单个线程。
notifyAll() -- 唤醒在此对象监视器上等待的所有线程。
wait() -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
wait()和notify()示例
下面通过示例演示"wait()和notify()配合使用的情形"。
// WaitTest.java的源码 class ThreadA extends Thread{ public ThreadA(String name) { super(name); } public void run() { synchronized (this) { System.out.println(Thread.currentThread().getName()+" call notify()"); // 唤醒当前的wait线程 notify(); } } } public class WaitTest { public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); synchronized(t1) { try { // 启动“线程t1” System.out.println(Thread.currentThread().getName()+" start t1"); t1.start(); // 主线程等待t1通过notify()唤醒。 System.out.println(Thread.currentThread().getName()+" wait()"); t1.wait(); System.out.println(Thread.currentThread().getName()+" continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行结果:
main start t1 main wait() t1 call notify() main continue
结果说明:
如下图,说明了“主线程”和“线程t1”的流程。
(01) 注意,图中"主线程" 代表“主线程main”。"线程t1" 代表WaitTest中启动的“线程t1”。 而“锁” 代表“t1这个对象的同步锁”。
(02) “主线程”通过 new ThreadA("t1") 新建“线程t1”。随后通过synchronized(t1)获取“t1对象的同步锁”。然后调用t1.start()启动“线程t1”。
(03) “主线程”执行t1.wait() 释放“t1对象的锁”并且进入“等待(阻塞)状态”。等待t1对象上的线程通过notify() 或 notifyAll()将其唤醒。
(04) “线程t1”运行之后,通过synchronized(this)获取“当前对象的锁”;接着调用notify()唤醒“当前对象上的等待线程”,也就是唤醒“主线程”。
(05) “线程t1”运行完毕之后,释放“当前对象的锁”。紧接着,“主线程”获取“t1对象的锁”,然后接着运行。
对于上面的代码?曾经有个朋友问到过:t1.wait()应该是让“线程t1”等待;但是,为什么却是让“主线程main”等待了呢?
在解答该问题前,我们先看看jdk文档中关于wait的一段介绍:
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
In other words, this method behaves exactly as if it simply performs the call wait(0). The current thread must own this object\'s monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object\'s monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.
中文意思大概是:
引起“当前线程”等待,直到另外一个线程调用notify()或notifyAll()唤醒该线程。换句话说,这个方法和wait(0)的效果一样!(补充,对于wait(long millis)方法,当millis为0时,表示无限等待,直到被notify()或notifyAll()唤醒)。 “当前线程”在调用wait()时,必须拥有该对象的同步锁。该线程调用wait()之后,会释放该锁;然后一直等待直到“其它线程”调用对象的同步锁的notify()或notifyAll()方法。然后,该线程继续等待直到它重新获取“该对象的同步锁”,然后就可以接着运行。
注意:jdk的解释中,说wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上运行的线程!
这也意味着,虽然t1.wait()是通过“线程t1”调用的wait()方法,但是调用t1.wait()的地方是在“主线程main”中。而主线程必须是“当前线程”,也就是运行状态,才可以执行t1.wait()。所以,此时的“当前线程”是“主线程main”!因此,t1.wait()是让“主线程”等待,而不是“线程t1”!
再看下一个例子:
package com.util.concurrent; public class WaitTest { public static void main(String[] args) { // TODO Auto-generated method stub ThreadA t1 = new ThreadA("t1"); String s1 = new String("test"); synchronized (t1) { try { System.out.println(Thread.currentThread().getName() + "start t1"); t1.start(); System.out.println(Thread.currentThread().getName() + "call wait()"); s1.wait(); System.out.println(Thread.currentThread().getName() + "continue"); } catch (Exception e) { // TODO: handle exception } } } } class ThreadA extends Thread{ public ThreadA(String name) { super(name); } public void run() { synchronized (this) { System.out.println(Thread.currentThread().getName() + "call notify()"); notify(); } } }
在这个例子中做了小小的修改,调用wait()的不是t1,而是一个新的对象s1,所以运行结果如下:
main start t1
main call wait()
t1 call notify()
主线程阻塞后就再也没起来,因为调用notify()的对象是t1,只会唤醒等待t1同步锁的线程,而main线程现在需要的是s1的notify。所以t1.wait() 的意思是让当前线程等待t1的同步锁,直到有其他线程调用了t1.notify,才会唤醒之前等待的线程。
wait(long timeout)和notify()
wait(long timeout)会让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
下面的示例就是演示wait(long timeout)在超时情况下,线程被唤醒的情况。
// WaitTimeoutTest.java的源码 class ThreadA extends Thread{ public ThreadA(String name) { super(name); } public void run() { System.out.println(Thread.currentThread().getName() + " run "); // 死循环,不断运行。 while(true) ; } } public class WaitTimeoutTest { public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); synchronized(t1) { try { // 启动“线程t1” System.out.println(Thread.currentThread().getName() + " start t1"); t1.start(); // 主线程等待t1通过notify()唤醒 或 notifyAll()唤醒,或超过3000ms延时;然后才被唤醒。 System.out.println(Thread.currentThread().getName() + " call wait "); t1.wait(3000); System.out.println(Thread.currentThread().getName() + " continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行结果:
main start t1 main call wait t1 run // 大约3秒之后...输出“main continue” main continue
结果说明:
如下图,说明了“主线程”和“线程t1”的流程。
(01) 注意,图中"主线程" 代表WaitTimeoutTest主线程(即,线程main)。"线程t1" 代表WaitTest中启动的线程t1。 而“锁” 代表“t1这个对象的同步锁”。
(02) 主线程main执行t1.start()启动“线程t1”。
(03) 主线程main执行t1.wait(3000),此时,主线程进入“阻塞状态”。需要“用于t1对象锁的线程通过notify() 或者 notifyAll()将其唤醒” 或者 “超时3000ms之后”,主线程main才进入到“就绪状态”,然后才可以运行。
(04) “线程t1”运行之后,进入了死循环,一直不断的运行。
(05) 超时3000ms之后,主线程main会进入到“就绪状态”,然后接着进入“运行状态”。
wait() 和 notifyAll()
通过前面的示例,我们知道 notify() 可以唤醒在此对象监视器上等待的单个线程。
下面,我们通过示例演示notifyAll()的用法;它的作用是唤醒在此对象监视器上等待的所有线程。
1 public class NotifyAllTest { 2 3 private static Object obj = new Object(); 4 public static void main(String[] args) { 5 6 ThreadA t1 = new ThreadA("t1"); 7 ThreadA t2 = new ThreadA("t2"); 8 ThreadA t3 = new ThreadA("t3"); 9 t1.start(); 10 t2.start(); 11 t3.start(); 12 13 try { 14 System.out.println(Thread.currentThread().getName()+" sleep(3000)"); 15 Thread.sleep(3000); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 20 synchronized(obj) { 21 // 主线程等待唤醒。 22 System.out.println(Thread.currentThread().getName()+" notifyAll()"); 23 obj.notifyAll(); 24 } 25 } 26 27 static class ThreadA extends Thread{ 28 29 public ThreadA(String name){ 30 super(name); 31 } 32 33 public void run() { 34 synchronized (obj) { 35 try { 36 // 打印输出结果 37 System.out.println(Thread.currentThread().getName() + " wait"); 38 39 // 唤醒当前的wait线程 40 obj.wait(); 41 42 // 打印输出结果 43 System.out.println(Thread.currentThread().getName() + " continue"); 44 } catch (InterruptedException e) { 45 e.printStackTrace(); 46 } 47 } 48 } 49 } 50 }
运行结果:
t1 wait main sleep(3000) t3 wait t2 wait main notifyAll() t2 continue t3 continue t1 continue
结果说明:
参考下面的流程图。
(01) 主线程中新建并且启动了3个线程"t1", "t2"和"t3"。
(02) 主线程通过sleep(3000)休眠3秒。在主线程休眠3秒的过程中,我们假设"t1", "t2"和"t3"这3个线程都运行了。以"t1"为例,当它运行的时候,它会执行obj.wait()等待其它线程通过notify()或额nofityAll()来唤醒它;相同的道理,"t2"和"t3"也会等待其它线程通过nofity()或nofityAll()来唤醒它们。
(03) 主线程休眠3秒之后,接着运行。执行 obj.notifyAll() 唤醒obj上的等待线程,即唤醒"t1", "t2"和"t3"这3个线程。 紧接着,主线程的synchronized(obj)运行完毕之后,主线程释放“obj锁”。这样,"t1", "t2"和"t3"就可以获取“obj锁”而继续运行了!
为什么notify(), wait()等函数定义在Object中,而不是Thread中
Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
OK,线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。
六,线程让步
yield()定义在Thread.java中。
yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
yield() 与 wait()的比较
我们知道,wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。而yield()的作用是让步,它也会让当前线程离开“运行状态”。它们的区别是:
(01) wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,而不yield()是让线程由“运行状态”进入到“就绪状态”。
(02) wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁。
下面通过示例演示yield()是不会释放锁的。
package com.util.concurrent; public class YieldTest { private static Object obj =new Object(); public static void main(String[] args) { ThreadB t1 = new ThreadB("t1"); ThreadB t2 = new ThreadB("t2"); t1.start(); t2.start(); } static class ThreadB extends Thread { public ThreadB(String name) { super(name); } public void run() { synchronized (obj) { for(int i=0; i<10; i++) { System.out.println(this.getName() + ":" + this.getPriority() + ":" + i); if(i%4 == 0) { Thread.yield(); } } } } } }
结果:
t1:5:0 t1:5:1 t1:5:2 t1:5:3 t1:5:4 t1:5:5 t1:5:6 t1:5:7 t1:5:8 t1:5:9 t2:5:0 t2:5:1 t2:5:2 t2:5:3 t2:5:4 t2:5:5 t2:5:6 t2:5:7 t2:5:8 t2:5:9
结果说明:
主线程main中启动了两个线程t1和t2。t1和t2在run()会引用同一个对象的同步锁,即synchronized(obj)。在t1运行过程中,虽然它会调用Thread.yield();但是,t2是不会获取cpu执行权的。因为,t1并没有释放“obj所持有的同步锁”!
七,线程休眠
sleep() 定义在Thread.java中。
sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。
sleep() 与 wait()的比较
我们知道,wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。而sleep()的作用是也是让当前线程由“运行状态”进入到“休眠(阻塞)状态”。
但是,wait()会释放对象的同步锁,而sleep()则不会释放锁。
下面通过示例演示sleep()是不会释放锁的。
package com.util.concurrent; public class SleepTest { private static Object obj = new Object(); public static void main(String[] args) { // TODO Auto-generated method stub ThreadC t1 = new ThreadC("t1"); ThreadC t2 = new ThreadC("t2"); t1.start(); t2.start(); } static class ThreadC extends Thread { public ThreadC(String name) { super(name); } public void run() { synchronized (obj) { for(int i=0; i<10; i++) { System.out.println(this.getName() + ":" + this.getPriority() + ":" + i); if(i%4 == 0) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } } }
结果:
t1:5:0 t1:5:1 t1:5:2 t1:5:3 t1:5:4 t1:5:5 t1:5:6 t1:5:7 t1:5:8 t1:5:9 t2:5:0 t2:5:1 t2:5:2 t2:5:3 t2:5:4 t2:5:5 t2:5:6 t2:5:7 t2:5:8 t2:5:9
主线程main中启动了两个线程t1和t2。t1和t2在run()会引用同一个对象的同步锁,即synchronized(obj)。在t1运行过程中,虽然它会调用Thread.sleep(100);但是,t2是不会获取cpu执行权的。因为,t1并没有释放“obj所持有的同步锁”!
注意,若我们注释掉synchronized (obj)后再次执行该程序,t1和t2是可以相互切换的。
八,join
join()定义在Thread.java中,与sleep()&yield()不同,它是一个实例方法,不是类方法。
join()的作用:让父线程等待子线程结束之后才能继续运行。这里的子线程必须是父线程创建出来的线程。
join()源码分析(基于JDK1.7.0_40)
public final void join() throws InterruptedException { join(0); } public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
说明:
从代码中,我们可以发现。当millis==0时,会进入while(isAlive())循环;即只要子线程是活的,主线程就不停的等待。
我们根据上面解释join()作用时的代码来理解join()的用法!
问题:
虽然s.join()被调用的地方是发生在“Father主线程”中,但是s.join()是通过“子线程s”去调用的join()。那么,join()方法中的isAlive()应该是判断“子线程s”是不是Alive状态;对应的wait(0)也应该是“让子线程s”等待才对。但如果是这样的话,s.join()的作用怎么可能是“让主线程等待,直到子线程s完成为止”呢,应该是让"子线程等待才对(因为调用子线程对象s的wait方法嘛)"?
答案:wait()的作用是让“当前线程”等待,而这里的“当前线程”是指当前在CPU上运行的线程。所以,虽然是调用子线程的wait()方法,但是它是通过“主线程”去调用的;所以,休眠的是主线程,而不是“子线程”!
示例:
// JoinTest.java的源码 public class JoinTest{ public static void main(String[] args){ try { ThreadA t1 = new ThreadA("t1"); // 新建“线程t1” t1.start(); // 启动“线程t1” t1.join(); // 将“线程t1”加入到“主线程main”中,并且“主线程main()会等待它的完成” System.out.printf("%s finish\\n", Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } static class ThreadA extends Thread{ public ThreadA(String name){ super(name); } public void run(){ System.out.printf("%s start\\n", this.getName()); // 延时操作 for(int i=0; i <1000000; i++) ; System.out.printf("%s finish\\n", this.getName()); } } }
结果:
t1 start t1 finish main finish
结果说明:
运行流程如图
(01) 在“主线程main”中通过 new ThreadA("t1") 新建“线程t1”。 接着,通过 t1.start() 启动“线程t1”,并执行t1.join()。
(02) 执行t1.join()之后,“主线程main”会进入“阻塞状态”等待t1运行结束。“子线程t1”结束之后,会唤醒“主线程main”,“主线程”重新获取cpu执行权,继续运行。
示例二:
package com.util.concurrent; public class JoinTest { public static void main(String[] args) { // TODO Auto-generated method stub ThreadD f = new ThreadD("f"); f.start(); } } class ThreadD extends Thread{ public ThreadD(String name) { super(name); } public void run() { ThreadDD s = new ThreadDD("s"); s.start(); try { s.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } for (int i = 0; i<20; i++) { System.out.println(Thread.currentThread().getName() + " is running" + " " + i); } } } class ThreadDD extends Thread{ public ThreadDD(String name) { super(name); } public void run() { for (int i = 0; i<20; i++) { System.out.println(Thread.currentThread().getName() + " is running" + " " + i); } } }
结果:
s is running 0 s is running 1 s is running 2 s is running 3 s is running 4 s is running 5 s is running 6 s is running 7 s is running 8 s is running 9 s is running 10 s is running 11 s is running 12 s is running 13 s is running 14 s is running 15 s is running 16 s is running 17 s is running 18 s is running 19 f is running 0 f is running 1 f is running 2 f is running 3 f is running 4 f is running 5 f is running 6 f is running 7 f is running 8 f is running 9 f is running 10 f is running 11 f is running 12 f is running 13 f is running 14 f is running 15 f is running 16 f is running 17 f is running 18 f is running 19
所以调用join之后,是调用者的父进程必须等待调用者进程结束。
九,线程终止方式
Thread中的stop()和suspend()方法,由于固有的不安全性,已经建议不再使用!可以使用interrupt的特性
interrupt()的作用是中断本线程。 本线程中断自己是被允许的;其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。 如果本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也会让它进入阻塞状态。若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。 如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。 如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为“true”。 中断一个“已终止的线程”不会产生任何操作。
1,终止处于“阻塞状态”的线程
通常,我们通过“中断”方式终止处于“阻塞状态”的线程。
当线程由于被调用了sleep(), wait(), join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。将InterruptedException放在适当的位置就能终止线程,形式如下:
@Override public void run() { try { while (true) { // 执行任务... } } catch (InterruptedException ie) { // 由于产生InterruptedException异常,退出while(true)循环,线程终止! } }
说明:在while(true)中不断的执行任务,当线程处于阻塞状态时,调用线程的interrupt()产生InterruptedException中断。中断的捕获在while(true)之外,这样就退出了while(true)循环!
注意:对InterruptedException的捕获务一般放在while(true)循环体的外面,这样,在产生异常时就退出了while(true)循环。否则,InterruptedException在while(true)循环体之内,就需要额外的添加退出处理。形式如下:
@Override public void run() { while (true) { try { // 执行任务... } catch (InterruptedException ie) { // InterruptedException在while(true)循环体内。 // 当线程产生了InterruptedException异常时,while(true)仍能继续运行!需要手动退出 break; } } }
说明:上面的InterruptedException异常的捕获在whle(true)之内。当产生InterruptedException异常时,被catch处理之外,仍然在while(true)循环体内;要退出while(true)循环体,需要额外的执行退出while(true)的操作。
2,终止处于“运行状态”的线程
通常,我们通过“标记”方式终止处于“运行状态”的线程。其中,包括“中断标记”和“额外添加标记”。
(01) 通过“中断标记”终止线程。
形式如下:
@Override public void run() { while (!isInterrupted()) { // 执行任务... } }
说明:isInterrupted()是判断线程的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时;可以调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()会返回true。此时,就会退出while循环。
注意:interrupt()并不会终止处于“运行状态”的线程!它会将线程的中断标记设为true。
(02) 通过“额外添加标记”。
形式如下:
private volatile boolean flag= true; protected void stopTask() { flag = false; } @Override public void run() { while (flag) { // 执行任务... } }
说明:线程中有一个flag标记,它的默认值是true;并且我们提供stopTask()来设置flag标记。当我们需要终止该线程时,调用该线程的stopTask()方法就可以让线程退出while循环。
注意:将flag定义为volatile类型,是为了保证flag的可见性。即其它线程通过stopTask()修改了flag之后,本线程能看到修改后的flag的值。
综合线程处于“阻塞状态”和“运行状态”的终止方式,比较通用的终止线程的形式如下:
@Override public void run() { try { // 1. isInterrupted()保证,只要中断标记为true就终止线程。 while (!isInterrupted()) { // 执行任务... } } catch (InterruptedException ie) { // 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。 } }
interrupt()常常被用来终止阻塞状态线程,示例:
package com.util.concurrent; public class InterruptedTest { public static void main(String[] args) { // TODO Auto-generated method stub try { Thread t1 = new MyInterruptedThread("t1"); System.out.println(t1.getName() +" ("+t1.getState()+") is new."); t1.start(); System.out.println(t1.getName() +" ("+t1.getState()+") is started."); Thread.sleep(100); t1.interrupt(); System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted."); Thread.sleep(300); System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now."); } catch (InterruptedException e) { e.printStackTrace(); } } } class MyInterruptedThread extends Thread{ public MyInterruptedThread(String name) { super(name); } public void run() { try { int i=0; while(true) { Thread.sleep(300); i++; System.out.println(Thread.currentThread().getName() + "(" + this.getState() + ") loop " + i); } //System.out.println(Thread.currentThread().getName() + "(" + this.getState() + ") loop " + i); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException."); } } }
结果:
t1 (NEW) is new.
t1 (RUNNABLE) is started.
t1 (TIMED_WAITING) is interrupted.
t1 (RUNNABLE) catch InterruptedException.
t1 (TERMINATED) is interrupted now.
可以通过额外添加标记的方式终止“运行状态”的线程,示例:
// Demo3.java的源码 class MyThread extends Thread { private volatile boolean flag= true; public void stopTask() { flag = false; } public MyThread(String name) { super(name); } @Override public void run() { synchronized(this) { try { int i=0; while (flag) { Thread.sleep(100); // 休眠100ms i++; System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i); } } catch (InterruptedException ie) { System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException."); } } } } public class Demo3 { public static void main(String[] args) { try { MyThread t1 = new MyThread("t1"); // 新建“线程t1” System.out.println(t1.getName() +" ("+t1.getState()+") is new."); t1.start(); // 启动“线程t1” System.out.println(t1.getName() +" ("+t1.getState()+") is started."); // 主线程休眠300ms,然后主线程给t1发“中断”指令。 Thread.sleep(300); t1.st以上是关于Java高级特性系列--多线程的主要内容,如果未能解决你的问题,请参考以下文章