工作中大家肯定都碰到过多线程,那么对于线程的几种状态你了解吗?首先,你得弄清楚线程有几种状态,然后才是怎么对各种状态的了解。
线程有6种状态,分别为NEW(初始),RUNNABLE(运行),WAITING(等待),TIME_WAITING(超时等待),BLOCKED(阻塞),TERMINATED(终止)。
一、NEW(初始)
一个线程创建好以后就是New状态
二、RUNNABLE(运行)
当调用线程的start()方法时线程就是Runnable,可能不太清楚的同学会说不应该是Running状态吗,其实Runnable包括Ready和Running两个状态,我们一般将这两个状态合成一个状态,即Runnable。其实Ready到Running状态的切换是根据系统调度来决定的,不是通过程序来控制的。但是Running到Ready却是可以通过程序来控制的,调用线程的yield()方法,让出当前线程占用CPU的时间片,这样当前线程的状态就是Ready状态了。
三、WAITING(等待)
调用当前线程的wait(),join()或者调用LockSupport.park()方法也能使线程进入Waiting状态。当然调用notify()、notifyall()或者LockSupport.unpark(Thread)会将线程从Waiting状态切换到Runnable状态。
LockSupport是java.util.concurrent.locks包下的,我们所知道的ReentrantLock就是调用LockSupport的park()方法进行挂起,也是进入到了Waiting状态。
至于Join()方法,是将执行当前线程的主方法进行阻塞,进入Waiting状态,然后当前线程执行完毕以后,线程的销毁之前会调用notifyall()方法唤醒所有处于Waiting()状态的线程,即main方法,然后main方法继续往下执行。
public class ThreadJoin {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("t1 is Running");
});
Thread t2 = new Thread(() -> {
System.out.println("t2 is Running");
});
Thread t3 = new Thread(() -> {
System.out.println("t3 is Running");
});
t1.start();
t2.start();
t3.start();
}
}
这样运行的话,结果t1,t2,t3出现的顺序可能各种各样,但是如果加入了join()方法
public class ThreadJoin {
public static void main(String[] args) throws Exception{
Thread t1 = new Thread(() -> {
System.out.println("t1 is Running");
});
Thread t2 = new Thread(() -> {
System.out.println("t2 is Running");
});
Thread t3 = new Thread(() -> {
System.out.println("t3 is Running");
});
t1.start();
t1.join();
t2.start();
t2.join();
t3.start();
}
}
结果发现,t1 is Running,t2 is Running,t3 is Running按照顺序打印出来了。
各位可以进入Thread中看一下join方法,
会调用执行当前线程的主线程,也就是main的wait()方法,将main线程的状态变为timed_waiting状态,所以只有t1先执行了,那么会有人问,后面的怎么执行了呢,你们有兴趣的可以去hotspot的源码下看一下,当t1执行结束系统会调用notifyall()方法唤醒处于Timed_waiting和waiting的线程,这时候main线程就会被唤醒,继续执行t2,下面的步骤也是跟上面的一样了,所以t1,t2,t3会按照顺序执行。
四、TIMED_WAITING(超时等待)
调用当前线程的sleep(long),wait(long),join(long)或者调用LockSupport.parkNanos()方法或LockSupport.parkUtil方法()也能使线程进入Timed_Waiting状态。当然调用notify()、notifyall()或者LockSupport.unpark(Thread)会将线程从Timed_Waiting状态切换到Runnable状态。
public class ThreadType {
public static void main(String[] args) {
new Thread(() -> {
while (true){
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"TIME_WAITING").start();
new Thread(() -> {
while (true){
try{
synchronized (ThreadType.class){
ThreadType.class.wait();
}
}catch (Exception e){
e.printStackTrace();
}
}
},"WAITING").start();
}
}
在idea的Terminal下用jps查看运行的进程
再用jstack 8468,可以看到
名字为WAITING的进程状态为WAITING,名字为TIME_WAITING的进程状态为TIMED_WAITING
五、BLOCKED(阻塞)
当对一个资源进行加Synchronized锁的时候,获取Synchronized锁的线程是处于Runnable状态的,而等待该线程释放锁的其它线程则处于Blocked状态,注意是其它等待锁的线程处于Blocked状态。
public class ThreadType {
public static void main(String[] args) {
new Thread(new SycClass(),"timed_waiting").start();
new Thread(new SycClass(),"blocked").start();
}
static class SycClass extends Thread{
public void run(){
synchronized (SycClass.class){
while (true){
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
同上,使用jps,然后jstack+进程号,可以看到
可以看到,timed_waiting拿到了SycClass类的锁,但是由于调用了sleep(1000),导致该线程处于TIMED_WAITING,而blocked线程由于等待timed_waiting释放锁,所以一直处于z
六、TERMINATED(终止)。
这个就很好理解了,线程运行结束就进入了Terminated状态。
七、其它问题
1.线程终止方法,我们讲的是应用层的线程终止
(1).加flag(设置一个成员变量,通过更改成员变量的变化来终止线程)
public class TerminateThread {
private volatile static Boolean flag = true;
public static void main(String[] args){
Thread t1 = new Thread(() ->{
while (flag){
System.out.println("running");
}
});
t1.start();
flag = false;
}
(2).使用interrupt()
public class TerminateThread {
public static void main(String[] args){
Thread t1 = new Thread(() ->{
while (!Thread.currentThread().isInterrupted()){
System.out.println("running");
}
});
t1.start();
t1.interrupt();
}
}
调用Thread.currentThread().isInterrupted()默认返回当前线程的native方法,返回一个false,但是当调用t1.interrupt()后,再调用 Thread.currentThread().isInterrupted()会返回一个true
(3)线程的stop()方法,但是不建议使用,因为相当于kill -9,使用这个把当前的进程都干掉了,面试的时候也不能说这个方法,是丢分项。
2.线程的复位
线程复位调用Thread.interrupted()即可将终止的标识由true变成false,复位到原始状态。
所有涉及线程阻塞的地方都会出现InterruptException异常,因为可能在阻塞的时候,调用interrupt()终止线程,但是因为处于阻塞状态,所以无法终止。而且就算捕获了这个异常,也是提醒你想终止,但是因为阻塞无法终止,因此,接下来的方法还是会继续进行。
想要终止就在获取异常的地方进行自己的处理。
3.锁的作用域
刚才提到了Synchronized方法,那么锁的作用域分为那几类呢?
(1)代码块锁
这个就很好理解了,这个就不解释了
(2)实例锁
public class SynDemo {
public void testScope(){
synchronized (this){
//TODO
}
}
public static void main(String[] args){
SynDemo s1 = new SynDemo();
SynDemo s2 = new SynDemo();
s1.testScope();
s2.testScope();
}
}
这个就是实例锁,Synchronized(this)中的this就是指当前的实例SynDemo,所以s1和s2是不同的实例,所以两者锁住的不是同一个实例,因此这样调用没有任何意义。
(3)全局锁
public class SynDemo {
public void testScope(){
synchronized (SynDemo.class){
//TODO
}
}
public static void main(String[] args){
SynDemo s1 = new SynDemo();
SynDemo s2 = new SynDemo();
s1.testScope();
s2.testScope();
}
}
这个就是全局锁了,锁住的是SynDemo这个类了,类的作用范围是整个进程,进程结束了类才被销毁,这个时候s1和s2调用testScope()就必须先获得锁,才能操作。
那么问题来了:
Integer t1 = new Integer(1);
public void testScope(){
synchronized (t1){
t1++;
System.out.println(t1);
}
}
这个能锁住t1吗???分范围,在-128~127之间能锁住,在这个范围外面锁不住,在 -128~127是在常量池中,是同一个实例,但是在这个范围外不是就不是从常量池中取了,而是每次重新创建,所以是不同的实例,锁不住了。
Integer count = new Integer(1);
Count++;
Sychronized(count)能不能锁住这个类
不能,因为Integer在-128到127是一个实例,但是在这之外是多个实例,锁不住了