前言:
今天各位老铁们对“js线程阻塞”大概比较重视,朋友们都需要剖析一些“js线程阻塞”的相关内容。那么小编同时在网络上网罗了一些关于“js线程阻塞””的相关文章,希望小伙伴们能喜欢,各位老铁们一起来了解一下吧!Life Goes On | 作者
urlify.cn/rIfI3m | 来源
一、状态简介
一个线程的生命周期里有五大状态,分别是:
新生就绪运行死亡运行后可能遇到的阻塞状态
二、相关方法
2.1 新生状态
Thread t = new Thread();
正如我们前面所说的,一个线程开始之后有自己的内存空间,这些工作空间和主内存进行交互,从主内存拷贝数据到工作空间。
当这个语句执行的时候,线程创建,开辟工作空间,也就是线程进入了新生状态。
2.2 就绪状态
普通情况,一旦调用了:
t.start();
start 方法被调用,线程立即进入了就绪状态,表示这个线程具有了运行的条件,但是还没有开始运行,这就是就绪状态。
线程就绪,但不意味着立即调度执行,因为要等待CPU的调度,一般来说是进入了就绪队列。
但是还有另外三种情况,线程也会进入就绪状态,四种分别是:
start()方法调用;本来处于阻塞状态,后来阻塞解除;如果运行的时候调用 yield() 方法,避免一个线程占用资源过多,中断一下,会让线程重新进入就绪状态。注意,如果调用 yield() 方法之后,没有其他等待执行的线程,此线程就会马上恢复执行;JVM 本身将本地线程切换到了其他线程,那么这个线程就进入就绪状态。2.3 运行状态
当CPU选定了一个就绪状态的线程,进行执行,这时候线程就进入了运行状态,线程真正开始执行线程体的具体代码块,基本是 run() 方法。
注意,一定是从 就绪状态 - > 运行状态,不会从阻塞到运行状态的。
2.4 阻塞状态
阻塞状态指的是代码不继续执行,而在等待,阻塞解除后,重新进入就绪状态。
也就是说,阻塞状态发生肯能是运行状态转过去的, 运行状态 - > 阻塞状态,不会从就绪状态转过去。
阻塞的方法有四种:
sleep()方法,是占用资源在睡觉的,可以限制等待多久;wait() 方法,和 sleep() 的不同之处在于,是不占用资源的,限制等待多久;join() 方法,加入、合并或者是插队,这个方法阻塞线程到另一个线程完成以后再继续执行;有些 IO 阻塞,比如 write() 或者 read() ,因为IO方法是通过操作系统调用的。
上面的方法和start() 一样,不是说调用了就立即阻塞了,而是看CPU。
2.5 死亡状态
死亡状态指的是,线程体的代码执行完毕或者中断执行。一旦进入死亡状态,不能再调用 start() 。
让线程进入死亡状态的方法是 stop() 和 destroy() 但是都不推荐使用,jdk里也写了已过时。
一般的做法是,在线程内,让线程自身自然死亡,或者加一些代码,想办法让线程执行完毕。
自然死亡:这个线程体里就是多少次的循环,几次调用,执行完了就完了。如果不能自然死亡:加一些终止变量,然后用它作为run的条件,这样,外部调用的时候根据时机,把变量设置为false。
比如下面的写法,第一种就是我们的正常写法(虽然很简单但是没有用lambda表达式,主要为了和第二种对比)
/* 终止线程的方法1:自然死亡*/public class Status implements Runnable{ @Override public void run() { for (int i=0; i<20; i++){ System.out.println("studying"); } } public static void main(String[] args) { Status s = new Status(); new Thread(s).start(); }}
/* 终止线程的方法2:外部控制*/public class Status implements Runnable{ //1.加入状态变量 private boolean flag = true; @Override //2.关联状态变量 public void run() { while (flag){ System.out.println("studying"); } } //3.对外提供状态变量改变方法 public void terminate() { this.flag = false; } public static void main(String[] args) { Status s = new Status();//1.新生 new Thread(s).start();//2.就绪,随后进入运行 //无人为阻塞 for (int i=0; i<100000; i++){ if (i==80000){ s.terminate();//3.终止 System.out.println("结束"); } } }}
三、阻塞状态详解
上面的内容,线程的五大状态里,其他四种都比较简单,创建对象的新生态、start开始的就绪态、cpu调度之后进入的运行态,以及正常结束或者外加干预导致的死亡态。
3.1 sleep()sleep(时间)指定当前线程阻塞的毫秒数:sleep存在异常:InterruptException;sleep时间到了之后线程进入就绪状态;sleep可以模拟网络延时、倒计时等;每一个对象都有一个无形的锁,sleep不会释放锁。(也就是我们说过的,抱着资源睡觉,这个特点对比wait)
前面用线程模拟抢票的网络延时,已经做过示例,就是用sleep设置线程阻塞的时间,达到网络延时的效果,让那个同步问题更容易显现出来。
我们再用龟兔赛跑的例子修改一下,前面兔子和乌龟都是一样各自跑,这次让兔子骄傲的爱睡觉,每隔一段路就睡一段时间。
public class Racer2 implements Runnable{ private String winner; @Override public void run() { for (int dis=1; dis<=100; dis++){ String role = Thread.currentThread().getName(); //模拟兔子睡觉 if (dis%10==0 && role.equals("兔子")){ try { Thread.sleep(500);//睡 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(role + " 跑了 " + dis); //每走一步,判断是否比赛结束 if (gameOver(dis))break; } } public boolean gameOver(int dis){ if (winner != null){ return true; } else if (dis == 100){ winner = Thread.currentThread().getName(); System.out.println("获胜者是 "+winner); return true; } return false; } public static void main(String[] args) { Racer2 racer = new Racer2();//1.创建实现类 new Thread(racer,"兔子").start();//2.创建代理类并start new Thread(racer,"乌龟").start(); }}
这里面:
Thread.sleep(500);//睡
就是让线程进入阻塞状态,并且 500 ms 后,自动进入就绪状态,由 cpu 调度适时重新运行。
也可以利用 sleep 模拟倒计时,不涉及多个线程,所以直接在主线程里面,主方法里写内容就可以,也不用run什么,直接调用sleep设置阻塞时间。
public class SleepTime { public static void main(String[] args) throws InterruptedException { int num = 10; while (num>=0){ Thread.sleep(1000); System.out.println(num--); } }}
这就会完成 10-0 的倒计时。
更花哨一点,用 Dateformat 和 Date 实现时间的显示:
public static void main(String[] args) throws InterruptedException { //获取十秒后的时间,然后倒计时 Date endTime = new Date(System.currentTimeMillis()+1000*10); //干预线程的结束 long end = endTime.getTime(); DateFormat format = new SimpleDateFormat("mm:ss"); while (true) { System.out.println(format.format(endTime)); Thread.sleep(1000); endTime = new Date(endTime.getTime() - 1000);//-1s if (end-10000 > endTime.getTime()){ break; } }}
3.2 wait() && notify()
和 sleep() 不同,wait() 方法和 notify() 搭配,可以互相搭配,一个经典的的使用场景以及介绍是在生产者-消费者模型中:
(待更新链接)
3.3 yield()
礼让线程,让当前正在执行的线程暂停,不阻塞线程,而是直接将线程从运行状态转入就绪状态,让cpu调度器重新调度。
用法和 sleep 是一样的,也是一个静态方法,调用起来的效果比 sleep 弱。
这是因为,礼让之余,还有 cpu 的调度在影响顺序,所以无法保证达到线程切换的效果, cpu 还是可能调用当前的线程。
3.4 join()
join 合并线程,待此线程执行完成后,再执行其他线程。也就是说,阻塞其他的所有线程,所以其实应该是插队线程,所以 join 应该翻译成加入,插入?。
不同于 sleep 和 yield 方法,join 不是静态方法,是一个普通方法,要通过一个具体的 Thread 对象才能调用 join。
public class JoinDemo { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { for (int i=0; i<100; i++){ System.out.println("myThread is joining" + i); } }); t.start(); for (int i=0; i<100; i++){ if (i == 50){ t.join();//插队,此时主线程不能执行,而要执行 t } System.out.println("main is running" + i); } }}
执行结果可以看出来,等到主线程执行到 i = 50后,t 线程完全执行,直到结束,才继续执行 main 线程。
(当然,在 i = 50 之前,是不确定的,cpu 给两个线程的安排)
join 的最重要的部分,就是插队可以保证,这个线程自己一定会先执行完,这是在很多地方需要的逻辑。
四、利用枚举变量监控线程状态
回过头看线程的状态转换图:
新生态、死亡态、除了阻塞内部,其他都已经进行了练习,其中,就绪态到运行态之间是不由程序员控制的,所以 java 给这两个状态了一个统一的名称叫 Runnable(不要和Runnable接口搞混)。
java jdk 里面对于线程状态的区分:
NEW 对应没有 Started 的线程,对应新生态;RUNNABLE,对于就绪态和运行态的合称;BLOCKED,WAITING,TIMED_WAITING三个都是阻塞态:sleep 和 join 称为WAITING,TIMED_WAITING(设置了时间的话);wait 和 IO 流阻塞称为BLOCKED。TERMINATED 死亡态。
我们用一个 demo 观察一下状态的切换过程:
public class AllState { public static void main(String[] args) throws InterruptedException { //一个拥有阻塞的线程 Thread t = new Thread(()->{ for (int i=0; i<10; i++){ try { Thread.sleep(200);//阻塞 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("........I am running........."); } }); System.out.println(t.getState());//获取状态,此时应该是new t.start(); System.out.println(t.getState());//获取状态,此时已经start //监控阻塞,设置结束条件为监控到 t 线程已经变成 Terminated while (!t.getState().equals(Thread.State.TERMINATED)){ Thread.sleep(200);//主线程每200ms监控一次线程 t System.out.println(t.getState()); } }}
从输出结果可以看到,从 开始的 new 到 runnable(start后),到有线程执行了 running 的runnable,到阻塞的 timed_waiting ,到恢复 runnable,到最终的结束 terminated。
Thread 还提供了线程数方法,可以计数,结束条件其实还可以改成对于线程数的判断,因为当 t 结束后,线程数就只剩下主线程了。
while (Thread.activeCount() != 1){ System.out.println(Thread.activeCount()); Thread.sleep(200);//主线程每200ms监控一次线程 t System.out.println(t.getState());}
然而,运行起来的时候输出显示的是 3 个线程:
最后 terminated 之后陷入了线程数是 2 的死循环,和预想的不一样。。。
引入了另一个问题,搜了一下,应该是控制台输出,也是一个线程被监控的。
标签: #js线程阻塞 #阻塞状态可以到运行状态吗 #java使线程进入阻塞状态