本文共 8633 字,大约阅读时间需要 28 分钟。
线程状态的扭转图如下:
new:新建状态,创建后尚未启动的线程处于这种状态
runnable:就绪状态,线程对象创建后,其他线程调用了该对象的start()方法后会进入该状态。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权
blocked:阻塞状态,线程在等待着获取到一个排他锁。在程序等待进入同步区域的时候,线程会进入这种状态。
waiting:无限期状态,不会分配CPU时间片,等待被其他线程显式唤醒。以下方法会让线程进入该状态:
- 没有设置Timeout参数的Object.wait()方法
- 没有设置Timeout参数的Thread.join()方法
- LockSupport.park()方法
time waiting:限期等待状态,不会分配CPU时间片,睡眠时间到或等待一定的时间后自动唤醒。以下方法会让线程进入该状态:
- Thread.sleep()方法
- 设置Timeout参数的Object.wait()方法
- 设置Timeout参数的Thread.join()方法
- LockSupport.parkNanos()方法
- LockSupport.parkUntil()方法
dead:死亡状态,线程正常执行完了或因异常退出了run()方法,该线程结束生命周期
currentThread()方法(常用)
currentThread()方法可以返回代码段正在被哪个线程调用的信息sleep()方法(常用)
Thread.sleep会暂停当前的线程进入阻塞状态,交出CPU,让CPU去执行其他的任务。sleep不会导致锁行为的改变,如果当前线程是拥有锁的,那么Thread.sleep不会让线程释放锁,也就是说如果当前线程持有对某个对象的锁(synchronized),其他线程也无法访问这个对象的。
sleep当睡眠时间到的时候,会根据OS调度重新获得CPU执行时间,但不一定会立即得到执行,因为此时CPU可能正在执行其他任务,所以调用sleep方法相当于让线程进入阻塞状态。如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出
因此注意:如果线程有携带锁,那么要把sleep放在锁外面,这要才会释放锁
代码示例:
public class SleepDemo { private int i = 0; private Object object = new Object(); public static void main(String[] args) throws IOException { SleepDemo sleepDemo = new SleepDemo(); MyThread thread1 = sleepDemo.new MyThread(); MyThread thread2 = sleepDemo.new MyThread(); thread1.start(); thread2.start(); } class MyThread extends Thread{ @Override public void run() { synchronized (object) { i++; System.out.println("i:"+i); try { System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态"); Thread.sleep(10000); } catch (InterruptedException e) { // TODO: handle exception } System.out.println("线程"+Thread.currentThread().getName()+"睡眠结束"); i++; System.out.println("i:"+i); } } }}
测试结果:
i:1线程Thread-0进入睡眠状态线程Thread-0睡眠结束i:2i:3线程Thread-1进入睡眠状态线程Thread-1睡眠结束i:4
从上面输出结果可以看出,当Thread-0进入睡眠状态之后,Thread-1并没有去执行具体的任务。只有当Thread-0执行完之后,此时Thread-0释放了对象锁,Thread-1才开始执行
yield()方法
调用yield方法会交出CPU,让CPU去执行其他线程。和sleep方法类似,不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程获取CPU执行时间的机会。
还有,调用yield方法并不会让线程进入阻塞状态,而是让线程重新回到runnable状态,它只需要等待重新获得CPU执行时间,因此有可能下一秒又开始执行,这也是和sleep方法不一样的
代码示例:
public class YieldDemo { private int i = 0; private Object object = new Object(); public static void main(String[] args) throws IOException { YieldDemo yieldDemo = new YieldDemo(); MyThread thread1 = yieldDemo.new MyThread(); thread1.start(); } class MyThread extends Thread{ @Override public void run() { long beginTime=System.currentTimeMillis(); int count=0; for (int i=0;i<50000000;i++){ count = count+(i+1); //Thread.yield(); } long endTime=System.currentTimeMillis(); System.out.println("用时:"+(endTime-beginTime)+" 毫秒!"); } }}
测试结果:
用时:21 毫秒!
如果将//Thread.yield();的注释去掉,执行结果如下
用时:4197 毫秒!
stop()、suspend()、resume()方法
过期的API,有很大的副作用,可能导致死锁或数据不一致interrupt()方法
Thread.interrupt()重置中断标志位为false,在后续的线程中断会详细描述start()方法(常用)
start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源run()方法
run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务getId()方法
getId()的作用是取得线程的唯一标识isAlive()方法
isAlive()的功能是判断当前线程是否处于活动状态代码示例:
public class IsAliveDemo { public static void main(String[] args) throws InterruptedException { IsAliveDemo isAliveDemo = new IsAliveDemo(); MyThread myThread = isAliveDemo.new MyThread(); System.out.println("begin == "+myThread.isAlive()); myThread.start(); System.out.println("end == "+myThread.isAlive()); } class MyThread extends Thread{ @Override public void run() { System.out.println("run == "+this.isAlive()); } }}
测试结果:
begin == falseend == truerun == true
方法isAlive()的作用是测试线程是否偶处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的
join()方法
在很多情况下,主线程创建并启动了线程,如果子线程中要进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。
方法join()的作用是等待线程对象销毁。Join()内部是通过while判断线程是否存活,如果存活则调用wait()方法进行等待的,直到该线程销毁死亡。在调用的时候会先获取该对象的锁(synchronized方式),然后调用wait(),然后释放锁
代码示例:
public class JoinDemo { public static void main(String[] args) throws InterruptedException { // 启动子进程 JoinDemo joinDemo = new JoinDemo(); joinDemo.new MyThread("new thread").start(); for (int i = 0; i < 10; i++) { if (i == 5) { MyThread th = joinDemo.new MyThread("joined thread"); th.start(); th.join(); } System.out.println(Thread.currentThread().getName() + " " + i); } } class MyThread extends Thread{ public MyThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(getName() + " " + i); } } }}
测试结果:
main 0main 1main 2main 3main 4new thread 0new thread 1new thread 2new thread 3new thread 4joined thread 0joined thread 1joined thread 2joined thread 3joined thread 4main 5main 6main 7main 8main 9
由上可以看出main主线程等待joined thread线程先执行完了才结束的。如果把th.join()这行注释掉,运行结果如下:
main 0main 1main 2main 3main 4main 5main 6main 7main 8main 9new thread 0new thread 1new thread 2new thread 3new thread 4joined thread 0joined thread 1joined thread 2joined thread 3joined thread 4
getName()和setName()
用来获得或设置线程名称getPriority()和setPriority()(不常用)
用来获取和设置线程优先级。成员变量priority控制优先级,范围1-10之间,数字越高优先级越高,缺省为5创建线程时setPriotity()可以设置优先级,但是不要指望他发挥作用,因为最终还是要看CPU时间片的执行先后
setDaemon()和isDaemon()(不常用)
用来设置线程是否成为守护线程和判断线程是否是守护线程。守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程
程序里没有非Daemon线程时,java程序就会退出
wait()方法(常用)
wait方法会暂停当前的线程,即在wait()所在的代码处停止执行,并交出CPU,让CPU去执行其他的任务,wait会释放锁。调用wait后,会进入阻塞状态,需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间。
注意:在使用wait方法之前,必须先获得该对象的锁,因此wait的使用通常都是如下:
synchronized (obj) { while (condition) obj.wait();}
notify()/notifyAll()(常用)
线程 A调用了对象O的wait方法进入等待状态,线程 B调用了对象O的notify方法进行唤醒,唤醒的是在对象O上wait的线程A。需要说明的是,线程B执行完notify后,不会马上释放对象锁,呈wait状态的线程A也不会马上获取该对象锁,要等到线程B将程序执行完,即退出synchronized代码块后,当前线程B才会释放锁,而线程A才可以获取该对象锁。
当第一个获得了唤醒的wait线程运行完毕之后,它会释放掉该对象锁,但是如果该对象没有再次使用notify语句,即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll
notify():唤醒一个线程,如果有多个同线程,则随机唤醒一个(不建议使用)
notiyfAll():所有在对象O上等待的线程全部唤醒(建议使用)总结:
- wait方法:java为每个Object都实现了wait方法,必须用在被synchronized同步的Object的临界区内。该方法可以使调用该方法的线程释放共享资源的锁,然后马上从运行状态退出,进入等待队列,直到被再次唤醒
- notify方法:java为每个Object都实现了notify方法,必须用在被synchronized同步的Object的临界区内。该方法可以随机唤醒等待队列中等待同一共享资源的一个线程,并使该线程退出等待队列,进入可运行状态,且本身线程会继续执行程序直到释放对象锁,该方法仅能通知一个线程
- notifyAll:是所有正在等待队列中等待同一资源的全部线程从等待状态退出,进入可运行状态。此时优先级最高的那个线程先执行,但也有可能是随机执行,取决于JVM的实现
interrupt()方法
中断线程,本质是将某线程的中断标志位设置为true,其他线程向某线程打招呼要中断它,那该线程是否理会由自己决定(使用Thread.getCurrent().isInterrupt()方法判断)。讲到这里,涉及中断的几个API我们重新来回顾一下:
- this.interrupt():属于对象的方法,在一个线程中调用另一个线程的interrupt()方法,即会向那个线程发出信号,表示线程中断状态已被设置。至于那个线程何去何从,由哪个线程具体的代码实现决定
- this.isInterrupted():属于对象的方法,用来判断当前线程的中断状态(true or false)
- Thread.interrupted():属于Thread的static方法,用来重置中断标志位为false
interrupt()不能中断在运行中的线程,它只能改变中断状态而已,至于是否继续执行还是退出线程由被中断的线程自行判断,可以通过isInterrupted()方法判断是否被中断
示例代码如下:
public class InterruptedDemo { class VisibilityTest extends Thread { public void run() { int i = 0; while(true) { //判断一下 if (Thread.currentThread().isInterrupted()) { System.out.println("interrupt"); System.out.println(i); break; } i++; } } } public static void main(String[] args) throws InterruptedException { InterruptedDemo demo = new InterruptedDemo(); VisibilityTest v = demo.new VisibilityTest(); v.start(); Thread.sleep(1000); v.interrupt();//中断 }}
上述情况可以根据isInterrupted()函数进行判断是否被中断,以便退出执行的线程。那如果该线程进入阻塞状态呢,比如调用sleep或await方法,该怎么办呢?这就是JDK在你使用sleep或await方法时,会强制要求你捕获InterruptException。因为在阻塞过程中,如果别的线程使用interrupt()方法进行中断该线程的话,那么会进入中断异常处理流程,这个时候就可顺利的退出线程,但此时中断标志位同时会被置为false,即isInterrupted()方法会返回false而不是true,此时被中断的线程的捕获异常处理代码中有判断自己的中断标志位时要注意!!
转载地址:http://hwpxi.baihongyu.com/