线程的概念,以及线程的创建方式,见我之前写的博文
本篇文章主要讲Java线程的6种状态
6种状态:初始状态(new) 、可运行状态(Runnable)、运行状态(Running)、阻塞状态(Blocked)、死亡状态(Dead)、等待队列
初始状态(New):
进程刚创建,未调用new Thread().start()方法时的状态,进程创建方式
可运行状态(Runnable):
在创建线程并调用start()方法之后,获得CPU运行时间之前的状态
进入Runnable状态的方式:
- 调用线程的start()方法
- 当前线程sleep()方法结束
- 其他线程join()结束
- 等待用户输入完毕(阻塞式IO,拿到IO结果后)
- 当前线程拿到所需锁
- 当前线程时间片耗尽
- 调用当前线程的yield()方法
运行中状态Running
获得CPU运行时间后,运行中的状态
进入Running状态的方式(唯一):
- 操作系统的线程调度程序,从可运行状态的线程池中,选出当前线程去执行;
阻塞状态Blocked
运行中的线程,可以会由于各种原因进入到阻塞状态。如调用sleep()方法让线程睡眠,调用wait()方法让线程等待,调用join()、suspend()方法(已弃用)或者阻塞式IO。
进入阻塞状态的方式:
- 当前线程调用sleep()方法
- 当前线程执行过程中,调用了其他线程的join()方法,当前线程进入阻塞状态
- 等待阻塞式IO时
死亡状态Dead
无论是正常退出(run()方法执行完毕),还是抛出了未捕获的异常,都会导致线程进入Dead状态。
或者是主线程main方法执行完毕
在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常
线程的状态流转图
线程睡眠:sleep()方法
线程睡眠,指的是让当前线程暂停执行,等到指定时间后再恢复执行(Runnable状态)
- 线程睡眠会交出CPU,让给其他线/进程执行
- sleep()方法不会释放锁,即当前线程持有某个对象锁时,即使调用sleep()方法其他线程也无法访问这个对象。
- 调用sleep(),当前线程从Running到blocked,sleep结束,从blocked到Runnable
public static native void sleep(long millis) throws InterruptedException;
线程让步:yield()方法
线程让步,指的是让当前线程暂停执行,并执行其他线程
与sleep()的区别在于
- yield()方法无法控制具体交出CPU的时间
- yield()方法只能让拥有相同优先级的线程获得CPU执行的机会
- yield()方法让当前线程进入Runnable状态
yield()方法:
public static native void yield();
等待线程终止:join()方法
等待线程终止,指的是:主线程中调用该方法,就会让调用该方法的线程先执行,等执行结束后,再回到主线程继续执行。(即等待,调用join()方法的线程执行完毕后,再执行)
join()方法:
public final void join() throws InterruptedException {//join带参数指的是等待的超时时间,为0代表永久等待join(0);}
wait()方法
obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒
public final void wait() throws InterruptedException {}public final native void wait(long timeout) throws InterruptedException;
notify()方法
obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程
public final native void notify();public final native void notifyAll();
线程停止的3种方式
1):设置标志位,让线程正常停止
class MyThread1 extends Thread {private boolean flag = false;@Overridepublic void run() {int i=0;while (!flag) {System.out.println("线程循环执行次数:"+i);}}
}
2):使用stop方法强制线程停止,不安全,已废弃!
stop方法:
@Deprecatedpublic final void stop() {}
为什么stop()不安全?
因为stop()会解除相应线程持有的所有锁,并且当在线程对象上执行stop方法时,会立即停止该线程
比如当正在执行如下同步方法时,执行到x=10,线程调用了stop()方法,那就会进入不一致的状态,所以线程不安全
public synchronized void f() {x =10;y =20;
}
3):调用interrupt()方法
调用Thread类的interrupted()方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。因此,通过interrupted()方法真正实现线程的中断原理是 :开发人员根据中断标志的具体值来决定如何退出线程。
interrupt()方法
public void interrupt() {}
拓展:
为什么notify(), wait()等函数定义在Object中,而不是Thread中
Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
OK,线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。