标签:解决 休眠 分享图片 second 解释 event 概念 nose 案例
自从我们学习了操作系统之后,对于其中的线程和进程就有了非常深刻的理解,但是,我们可能在C,C++语言之中尝试过这些机制,并且做过相应的实验,但是对于java的多线程机制以及其中延伸出来的很多概念和相应的实现方式一直都是模棱两可的,虽然后来在面试的时候可能恶补了一些这方面的知识,但是也只是当时记住了,或者了解了一些,等到以后就会变得越来越淡忘了,比如线程的实现方式有两三种,线程池的概念,线程的基本生命周期等等,以及关于线程之间的多并发引起的资源的抢占和竞争,锁的出现,同步和异步,阻塞等等,这些概念再往下面延伸就到了jvm这种虚拟机的内存管理层面上了,由此又出现了jvm的生存周期,内存组成,函数调用,堆和栈,缓存,volatile共享变量等等机制,至此我们才能很好的理解多线程和并发。
让我们看看网上对多线程生命周期的描述:
Java线程具有五中基本状态:
1 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread(); 2 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,
随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行; 3 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。
注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中; 4 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。
根据阻塞产生的原因不同,阻塞状态又可以分为三种: 5 1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态; 6 2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态; 7 3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。
当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 8 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
这种解释其实和我们在操作系统中学习的是一致的,只不过内部的实现方式有所不同而已,同样的如果实在Linux之中,进程和线程的生命周期有略微有所不同,但是究其根源来说都是这几种步骤,只不过在某种过程之下可能有所细分而已。
再比如说其他资料上对java的多线程生命周期的划分,我们也可以看到就是把其中的阻塞状态分离出来而已:
明白了这一点,对于我们继续细分其中的状态背后的意义至关重要。
新启一个线程执行其run()方法,一个线程只能start一次。主要是通过调用native start0()来实现。
1 public synchronized void start() { 2 //判断是否首次启动 3 if (threadStatus != 0) 4 throw new IllegalThreadStateException(); 5 6 group.add(this); 7 8 boolean started = false; 9 try { 10 //启动线程 11 start0(); 12 started = true; 13 } finally { 14 try { 15 if (!started) { 16 group.threadStartFailed(this); 17 } 18 } catch (Throwable ignore) { 19 /* do nothing. If start0 threw a Throwable then 20 it will be passed up the call stack */ 21 } 22 } 23 } 24 private native void start0();
run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当该线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,如果继承Thread类则必须重写run方法,在run方法中定义具体要执行的任务。
sleep方法有两个重载版本:
1 sleep(long millis) //参数为毫秒 2 sleep(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
join方法有三个重载版本:
1 join() 2 join(long millis) //参数为毫秒 3 join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
join()实际是利用了wait(),只不过它不用等待notify()/notifyAll(),且不受其影响。它结束的条件是:1)等待时间到;2)目标线程已经run完(通过isAlive()来判断)。
1 public final synchronized void join(long millis) throws InterruptedException { 2 long base = System.currentTimeMillis(); 3 long now = 0; 4 5 if (millis < 0) { 6 throw new IllegalArgumentException("timeout value is negative"); 7 } 8 9 //0则需要一直等到目标线程run完 10 if (millis == 0) { 11 while (isAlive()) { 12 wait(0); 13 } 14 } else { 15 //如果目标线程未run完且阻塞时间未到,那么调用线程会一直等待。 16 while (isAlive()) { 17 long delay = millis - now; 18 if (delay <= 0) { 19 break; 20 } 21 wait(delay); 22 now = System.currentTimeMillis() - base; 23 } 24 } 25 }
此操作会中断等待中的线程,并将线程的中断标志位置位。如果线程在运行态则不会受此影响。
可以通过以下三种方式来判断中断:
1)isInterrupted() 此方法只会读取线程的中断标志位,并不会重置。 2)interrupted() 此方法读取线程的中断标志位,并会重置。 3)throw InterruptException 抛出该异常的同时,会重置中断标志位。
通常,我们通过“中断”方式终止处于“阻塞状态”的线程。当线程由于被调用了sleep(), wait(), join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。将InterruptedException放在适当的为止就能终止线程,形式如下:
1 @Override 2 public void run() { 3 try { 4 while (true) { 5 // 执行任务... 6 } 7 } catch (InterruptedException ie) { 8 // 由于产生InterruptedException异常,退出while(true)循环,线程终止! 9 } 10 }
在while(true)中不断的执行任务,当线程处于阻塞状态时,调用线程的interrupt()产生InterruptedException中断。中断的捕获在while(true)之外,这样就退出了while(true)循环!对InterruptedException的捕获务一般放在while(true)循环体的外面,这样,在产生异常时就退出了while(true)循环。否则,InterruptedException在while(true)循环体之内,就需要额外的添加退出处理。
1 @Override 2 public void run() { 3 while (true) { 4 try { 5 // 执行任务... 6 } catch (InterruptedException ie) { 7 // InterruptedException在while(true)循环体内。 8 // 当线程产生了InterruptedException异常时,while(true)仍能继续运行!需要手动退出 9 break; 10 } 11 } 12 }
上面的InterruptedException异常的捕获在whle(true)之内。当产生InterruptedException异常时,被catch处理之外,仍然在while(true)循环体内;要退出while(true)循环体,需要额外的执行退出while(true)的操作。
通常,我们通过“标记”方式终止处于“运行状态”的线程。其中,包括“中断标记”和“额外添加标记”。
通过“中断标记”终止线程:
1 @Override 2 public void run() { 3 while (!isInterrupted()) { 4 // 执行任务... 5 } 6 }
isInterrupted()是判断线程的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时;可以调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()会返回true。此时,就会退出while循环。注意interrupt()并不会终止处于“运行状态”的线程!它会将线程的中断标记设为true。
通过“额外添加标记”终止处于“运行状态”的线程,线程中有一个flag标记,它的默认值是true;并且我们提供stopTask()来设置flag标记。当我们需要终止该线程时,调用该线程的stopTask()方法就可以让线程退出while循环。注意将flag定义为volatile类型,是为了保证flag的可见性。即其它线程通过stopTask()修改了flag之后,本线程能看到修改后的flag的值。
1 private volatile boolean flag= true; 2 protected void stopTask() { 3 flag = false; 4 } 5 @Override 6 public void run() { 7 while (flag) { 8 // 执行任务... 9 } 10 }
综合线程处于“阻塞状态”和“运行状态”的终止方式,比较通用的终止线程的形式如下:
1 @Override 2 public void run() { 3 try { 4 // 1. isInterrupted()保证,只要中断标记为true就终止线程。 5 while (!isInterrupted()) { 6 // 执行任务... 7 } 8 } catch (InterruptedException ie) { 9 // 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。 10 } 11 }
1 package com.thread.test; 2 3 class MyThread extends Thread { 4 5 public MyThread(String name) { 6 super(name); 7 } 8 9 @Override 10 public void run() { 11 try { 12 int i=0; 13 while (!isInterrupted()) { 14 Thread.sleep(100); // 休眠100ms 15 i++; 16 System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i); 17 } 18 } catch (InterruptedException e) { 19 System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException."); 20 } 21 } 22 } 23 24 public class Test1 { 25 26 public static void main(String[] args) { 27 try { 28 Thread t1 = new MyThread("t1"); // 新建“线程t1” 29 System.out.println(t1.getName() +" ("+t1.getState()+") is new."); 30 31 t1.start(); // 启动“线程t1” 32 System.out.println(t1.getName() +" ("+t1.getState()+") is started."); 33 34 // 主线程休眠300ms,然后主线程给t1发“中断”指令。 35 Thread.sleep(300); 36 t1.interrupt(); 37 System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted."); 38 39 // 主线程休眠300ms,然后查看t1的状态。 40 Thread.sleep(300); 41 System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now."); 42 } catch (InterruptedException e) { 43 e.printStackTrace(); 44 } 45 } 46 }
1 package com.thread.test; 2 3 class MyThread1 extends Thread { 4 5 public MyThread1(String name) { 6 super(name); 7 } 8 9 @Override 10 public void run() { 11 int i=0; 12 while (!isInterrupted()) { 13 try { 14 Thread.sleep(100); // 休眠100ms 15 } catch (InterruptedException ie) { 16 System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException."); 17 } 18 i++; 19 System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i); 20 } 21 } 22 } 23 24 public class Test2 { 25 26 public static void main(String[] args) { 27 try { 28 Thread t1 = new MyThread1("t1"); // 新建“线程t1” 29 System.out.println(t1.getName() +" ("+t1.getState()+") is new."); 30 31 t1.start(); // 启动“线程t1” 32 System.out.println(t1.getName() +" ("+t1.getState()+") is started."); 33 34 // 主线程休眠300ms,然后主线程给t1发“中断”指令。 35 Thread.sleep(300); 36 t1.interrupt(); 37 System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted."); 38 39 // 主线程休眠300ms,然后查看t1的状态。 40 Thread.sleep(300); 41 System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now."); 42 } catch (InterruptedException e) { 43 e.printStackTrace(); 44 } 45 } 46 }
1 t1 (NEW) is new. 2 t1 (RUNNABLE) is started. 3 t1 (RUNNABLE) loop 1 4 t1 (RUNNABLE) loop 2 5 t1 (TIMED_WAITING) is interrupted. 6 t1 (RUNNABLE) catch InterruptedException. 7 t1 (RUNNABLE) loop 3 8 t1 (RUNNABLE) loop 4 9 t1 (RUNNABLE) loop 5 10 t1 (TIMED_WAITING) is interrupted now. 11 t1 (RUNNABLE) loop 6 12 t1 (RUNNABLE) loop 7 13 t1 (RUNNABLE) loop 8 14 t1 (RUNNABLE) loop 9 15 t1 (RUNNABLE) loop 10 16 t1 (RUNNABLE) loop 11 17 t1 (RUNNABLE) loop 12 18 t1 (RUNNABLE) loop 13 19 t1 (RUNNABLE) loop 14 20 t1 (RUNNABLE) loop 15 21 t1 (RUNNABLE) loop 16 22 t1 (RUNNABLE) loop 17 23 t1 (RUNNABLE) loop 18 24 t1 (RUNNABLE) loop 19 25 t1 (RUNNABLE) loop 20 26 t1 (RUNNABLE) loop 21 27 t1 (RUNNABLE) loop 22 28 t1 (RUNNABLE) loop 23 29 t1 (RUNNABLE) loop 24 30 t1 (RUNNABLE) loop 25 31 t1 (RUNNABLE) loop 26 32 t1 (RUNNABLE) loop 27 33 t1 (RUNNABLE) loop 28 34 t1 (RUNNABLE) loop 29 35 t1 (RUNNABLE) loop 30 36 t1 (RUNNABLE) loop 31 37 t1 (RUNNABLE) loop 32 38 t1 (RUNNABLE) loop 33 39 t1 (RUNNABLE) loop 34 40 t1 (RUNNABLE) loop 35 41 t1 (RUNNABLE) loop 36 42 。。。。。。
程序进入了死循环,这是因为t1在“等待(阻塞)状态”时,被interrupt()中断;此时,会清除中断标记[即isInterrupted()会返回false],而且会抛出InterruptedException异常(该异常在while循环体内被捕获)。因此,t1理所当然的会进入死循环了。解决该问题,需要我们在捕获异常时,额外的进行退出while循环的处理。例如,在MyThread的catch(InterruptedException)中添加break 或 return就能解决该问题。
1 package com.thread.test; 2 3 class MyThread3 extends Thread { 4 5 private volatile boolean flag= true; 6 public void stopTask() { 7 flag = false; 8 } 9 10 public MyThread3(String name) { 11 super(name); 12 } 13 14 @Override 15 public void run() { 16 synchronized(this) { 17 try { 18 int i=0; 19 while (flag) { 20 Thread.sleep(100); // 休眠100ms 21 i++; 22 System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i); 23 } 24 } catch (InterruptedException ie) { 25 System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException."); 26 } 27 } 28 } 29 } 30 31 public class Test3 { 32 33 public static void main(String[] args) { 34 try { 35 MyThread3 t1 = new MyThread3("t1"); // 新建“线程t1” 36 System.out.println(t1.getName() +" ("+t1.getState()+") is new."); 37 38 t1.start(); // 启动“线程t1” 39 System.out.println(t1.getName() +" ("+t1.getState()+") is started."); 40 41 // 主线程休眠300ms,然后主线程给t1发“中断”指令。 42 Thread.sleep(300); 43 t1.stopTask(); 44 System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted."); 45 46 // 主线程休眠300ms,然后查看t1的状态。 47 Thread.sleep(300); 48 System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now."); 49 } catch (InterruptedException e) { 50 e.printStackTrace(); 51 } 52 } 53 }
挂起线程,直到被resume,才会苏醒。但调用suspend()的线程和调用resume()的线程,可能会因为争锁的问题而发生死锁,所以JDK 7开始已经不推荐使用了。Thread中的stop()和suspend()方法,由于固有的不安全性,已经建议不再使用!
参考文献:https://www.cnblogs.com/skywang12345/p/3479949.html
https://www.cnblogs.com/lwbqqyumidi/p/3804883.html
标签:解决 休眠 分享图片 second 解释 event 概念 nose 案例
原文地址:https://www.cnblogs.com/zyrblog/p/9869972.html