多线程系列(一)--线程基础
2018-01-09 15:49
155 查看
文章目录
(一)线程的定义
(二)实现多线程的方式
继承Thread类
实现Runnable接口
(三)Thread常用方法介绍
中断线程方法
interrupt()方法
静态方法
currentThread()方法
sleep()方法
yield()方法
对象方法
isAlive()方法
join()方法
join(long)方法与sleep(long)方法的区别
(四)停止线程
安全的终止线程
中断法+boolean变量法
抛出异常法+return法
(五)线程优先级
(六)守护线程(Daemon线程)
(七)线程的状态(线程的生命周期)
实现Runnable接口;
Thread类实现了Runnable接口,他们之间具有多态关系。
下面是一个创建线程实例,继承Thread类,并且重写run方法。
运行结果:
从运行结果可以看出,myThread.run()方法执行的时间比较晚,说明在使用多线程技术时,代码运行的结果与代码执行的顺序无关。
![](http://upload-images.jianshu.io/upload_images/9953255-cf80b14012e620ff.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
通过实现Runnable接口,也可以创建线程,将Runnable对象通过Thread类的构造函数传进去,下面展示实例:
运行结果:
因为Java只支持单继承,为了避免这一局限性,可以使用实现Runnable接口的方式来实现多线程技术。
从构造函数Thread(Runnable target)可以看出,我们不光可以传入实现Runnable接口的对象,也可以传入一个Thread类的对象,因为Thread类也实现了Runnable接口,这样做可以将Thraed对象中的run()方法完全交由其他的线程来进行调用。
中断可以理解为线程的一个标识位属性,它表示一个运行中线程是否被其他线程进行了中断操作。其他线程通过调用该线程的interrupt()方法来对它进行中断操作。
方法说明:
1.如果被中断的线程正在调用Object.wait()/Object.wait(long)/Object.wait(long,int),Thread.join()/Thread.join(long)/Thread.join(long,int),Thread.sleep(long)/Thread.sleep(long,int),那么中断状态将会被清除(中断状态置为false),并且抛出InterruptedException异常。
举例说明:
运行结果:
结果分析:
busyThread线程与main线程结果对比验证:
busyThread线程中断之后,调用Thread的对象方法isInterrupted()获取busyThread线程的中断状态为true,再次调用还是为true,说明isInterrupted()方法不会重置中断标志位。
main线程中断之后,调用Thread.interrupted()方法,第一次返回true,第二次返回false,验证第一次调用Thread.interrupted()方法之后会重置该线程的中断标志位,因此第二次调用的时候返回结果为false.
sleepThread线程与busyThread线程结果对比:
busyThread线程一直在运行,中断之后,线程正常中断,而sleepThread线程一直在睡眠,中断之后会抛出InterruptedException异常;
sleepThread线程中断之后调用interrupted()方法,两次都返回false,验证在该线程正在sleep()的时候中断线程,在抛出异常之前会先清除中断标志位,即中断标识置为false。
JDK中实现:
举例说明:
运行结果:
结果说明:main()方法被名为main的线程调用。
再次举例说明:
运行结果:
结果说明:
Run1的构造方法被main线程调用,run方法被thread-0的线程调用,说明run1.start()方法会新启一个线程。将上述代码中的run1.run()的注释取消,再次执行,得到的结果如下:
从上面的结果可以看出,直接调用线程的run方法run1.run(),该方法是由main线程来执行的,并没有新启一个线程。
JDK实现:
方法说明:
暂停线程,线程进入到waiting状态,会让出CPU。
不会放弃对象锁。
静态方法,作用在当前线程上。
举例说明:
运行结果:
结果说明:因为main线程与Run3线程是异步执行的,所以main线程可能先执行完,打印了main的begin和end,Run3线程随后执行,打印run begin和run end.
JDK实现:
举例说明:
运行结果:
取消掉上述代码注释后运行结果:
在JDK中实现如下:
举例说明:
运行结果:
上述结果说明:在线程run2没有执行start()方法的时候,线程还未运行,isAlive()=false,在执行start()之后,这里的结果其实是不确定的,end == true说明该线程还没有运行完成,因此isAlive=true,在睡眠1s之后,Thread end == false说明线程已经运行完成,所以isAlive==false.
join()方法的实现调用了wait()方法,当线程终止时,会调用线程自身的notifyAll()方法,会通知所有等待在该线程对象上的线程。JDK实现如下:
从源码可以看出:如果线程被生成了,但还未被起动,isAlive()将返回false,调用它的join()方法是没有作用的。将直接继续向下执行。
当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁。
举例说明:
运行结果:
方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)具有释放对象锁的特点。从源码中可以看出,当执行wait(long)方法后,当前线程的锁被释放,那么其他线程就可以调用此线程的同步方法了。而Thread.sleep(long)方法却不释放锁。
stop()方法-已作废,强制停止线程
stop()方法在终结一个线程时不会保证线程的资源正常释放,没有给予线程完成资源释放工作的机会,可能会导致程序工作在不确定的状态下。
suspend()方法-已作废,暂停线程
resume()方法-已作废,恢复线程
suspend()/resume()方法在调用后,线程不会释放已经占有的资源(比如锁),而是占着资源进入睡眠状态,这样荣引起死锁问题。还容易出现因为线程的暂停而导致数据不同步的情况。
实例如下:
运行结果:
从上面的结果看出,run()方法里面,while循环之外,还是会继续运行,打印count还是能够打印出来。
为了解决上述问题,还有一种方式来终止线程。
运行结果(注释 // throw new InterruptedException();):
运行结果(去掉注释 // throw new InterruptedException()):
结果分析:用抛出异常的方式,没有打印出”below for”.
运行结果:
结果分析:同样没有”below for”.
设置线程的优先级使用setPriority(int)方法,默认优先级是5.在Java中,线程的优先级分为1~10这10个等级,如果小于1或者大于10,则JDK抛出异常throw new IllegalArgumentException()。设置优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。
JDK中使用3个常量来预置定义优先级的值。如下:
线程优先级的特性:
继承性
在Java中,线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A是一样的。
规则性
线程的优先级具有一定的规则性,也就是CPU尽量将执行资源让给优先级比较高的线程。
随机性
线程的优先级具有随机性,也就是优先级较高的线程不一定每次都先执行完。当有大量优先级高的线程和大量优先级低的线程一起执行的时候,呈现出来的是大多数的优先级高的线程先执行完,但是不是每一个高优先级的线程都比低优先级的线程先执行完。
Daemon线程是一种支持性线程,因为他主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。Daemon属性需要在启动线程之前设置,不能在启动之后设置。
典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程也就没有存在的必要了,自动销毁。Daemon线程的作用是为其他线程的运行提供便利服务。
Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块并不一定会执行,实例如下:
运行结果:无
结果说明:
运行上述程序,可以看到在中断或者命令提示符上没有任何输出。main线程(非Daemon线程)在启动了DaemonThread之后随着main方法执行完毕而终止,而此时Java虚拟机中已经没有非Daemon线程,虚拟机需要退出。Java虚拟机中的所有Daemon线程都需要立即终止,因此DaemonThread立即终止,但是DaemonThread中的finally块并没有执行。在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。
在Java中线程的状态分为6种
1. 初始状态:new
创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
2. 运行态:runnable
在java中,运行态包含就绪态和运行态。
就绪态
该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就可以运行。
所有就绪状态的线程存放在就绪队列中。
运行态
获得CPU执行权,正在执行的线程。
由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程。
3.阻塞态:blocked
当一条正在执行的线程请求你某一资源失败时,就会进入阻塞态。
在Java中,阻塞态专指请求锁失败时进入的状态。(进入synchronized关键字修饰的方法或代码块时,获取锁失败)
由一个阻塞队列存放所有阻塞态的线程。
处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待 执行。
4.等待态:waiting
当前线程调用wait/join/park函数时,当前线程就会进入等待态。
也有一个等待队列存放所有等待态的线程。
线程处于等待态标识它需要等待其他线程的指示才能继续运行。
进入等待态的线程会释放CPU执行权,并释放资源(如锁)。
5.超时等待态:timed_waiting
当运行中的线程调用sleep(time)/wait/join/parkNanos/parkUntil时,就会进入该状态。
它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒。
进入该状态后会释放CPU执行权和占有的资源。
与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
6.终止态:terminated
线程执行结束后的状态。
阻塞在java.concurrent包中的Lock接口的线程是等待状态,因为java.concurrent包中的Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。
Java线程各状态之间的转换如下图所示:
![](http://upload-images.jianshu.io/upload_images/9953255-25118ccd0d9d4eb5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
参考:
Java多线程编程核心技术
Java并发编程的的艺术
该文是我的读书笔记,对知识进行总结,帮助自己以后回顾。
(一)线程的定义
(二)实现多线程的方式
继承Thread类
实现Runnable接口
(三)Thread常用方法介绍
中断线程方法
interrupt()方法
静态方法
currentThread()方法
sleep()方法
yield()方法
对象方法
isAlive()方法
join()方法
join(long)方法与sleep(long)方法的区别
(四)停止线程
安全的终止线程
中断法+boolean变量法
抛出异常法+return法
(五)线程优先级
(六)守护线程(Daemon线程)
(七)线程的状态(线程的生命周期)
线程的定义
线程可以理解成是在进程中独立运行的子任务。实现多线程的方式
继承Thread类;实现Runnable接口;
继承Thread类
public class Thread implements Runnable
Thread类实现了Runnable接口,他们之间具有多态关系。
下面是一个创建线程实例,继承Thread类,并且重写run方法。
public class MyThread extends Thread { @Override public void run(){ super.run(); System.out.println("MyThread"); } public static void main(String[]args){ MyThread myThread=new MyThread(); myThread.start(); System.out.println("main running end!"); } }
运行结果:
main running end! MyThread
从运行结果可以看出,myThread.run()方法执行的时间比较晚,说明在使用多线程技术时,代码运行的结果与代码执行的顺序无关。
实现Runnable接口
首先,我们来查看一下Thread.java的构造函数:![](http://upload-images.jianshu.io/upload_images/9953255-cf80b14012e620ff.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
通过实现Runnable接口,也可以创建线程,将Runnable对象通过Thread类的构造函数传进去,下面展示实例:
public class MyRunnable implements Runnable { @Override public void run() { System.out.println("running!"); } public static void main(String[]args){ Runnable runnable=new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); System.out.println("main running end!"); } }
运行结果:
main running end! running!
因为Java只支持单继承,为了避免这一局限性,可以使用实现Runnable接口的方式来实现多线程技术。
从构造函数Thread(Runnable target)可以看出,我们不光可以传入实现Runnable接口的对象,也可以传入一个Thread类的对象,因为Thread类也实现了Runnable接口,这样做可以将Thraed对象中的run()方法完全交由其他的线程来进行调用。
Thread常用方法介绍
方法名称 | 方法说明 |
---|---|
public synchronized void start() | 启动一个线程,Java虚拟机JVM调用run()方法 |
public final native boolean isAlive(); | isAlive()方法是判断当前线程是否处于活动状态。活动状态是线程已经启动且尚未终止。 |
public 1389d void interrupt() | 中断线程 |
public boolean isInterrupted() | 测试线程Thread对象是否中断,但是不会清除中断状态,即再次调用this.isInterrupted()方法,返回true. |
public final synchronized void join(long millis); public final void join(); public final synchronized void join(long millis, int nanos); | 如果线程A执行了thread.join()语句,则当前线程A等待thread线程终止之后才从thread.join()返回。两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从超时方法中返回。 |
public static boolean interrupted() | 测试当前线程是否中断,执行后会清除中断状态(中断状态会置为false),即第一次调用返回true,再次调用Thread.interrupted()方法,返回false. |
public static native Thread currentThread(); | 返回对当前正在执行的线程对象的引用。 |
public static native void sleep(long millis); public static void sleep(long millis, int nanos) | sleep()方法是让当前线程“正在执行的线程”休眠(暂停)指定的毫秒数。 |
public static native void yield(); | 让当前线程放弃当前的CPU资源,将CPU让给其他的任务去占用CPU执行时间。由running状态变为ready状态 |
中断线程方法
方法名称 | 方法说明 |
---|---|
public void interrupt() | 中断线程 |
public boolean isInterrupted() | 测试线程Thread对象是否中断,但是不会清除中断状态,即再次调用this.isInterrupted()方法,返回true. |
public static boolean interrupted() | 测试当前线程是否中断,执行后会清除中断状态(中断状态会置为false),即第一次调用返回true,再次调用Thread.interrupted()方法,返回false. |
interrupt()方法
interrupt()方法的作用是中断线程。对象方法。方法说明:
1.如果被中断的线程正在调用Object.wait()/Object.wait(long)/Object.wait(long,int),Thread.join()/Thread.join(long)/Thread.join(long,int),Thread.sleep(long)/Thread.sleep(long,int),那么中断状态将会被清除(中断状态置为false),并且抛出InterruptedException异常。
举例说明:
public class InterruptThread{ public static void main(String[]args){ try{ SleepThread sleepThread=new SleepThread(); BusyThread busyThread=new BusyThread(); sleepThread.start(); busyThread.start(); Thread.sleep(1000); sleepThread.interrupt(); busyThread.interrupt(); System.out.println("busyThread isInterrupted="+busyThread.isInterrupted()); System.out.println("busyThread isInterrupted="+busyThread.isInterrupted()); System.out.println("sleepThread isInterrupted="+sleepThread.isInterrupted()); System.out.println("sleepThread isInterrupted="+sleepThread.isInterrupted()); Thread.currentThread().interrupt(); System.out.println("main interrupted="+Thread.interrupted()); System.out.println("main interrupted="+Thread.interrupted()); } catch (InterruptedException e) { e.printStackTrace(); } } public static class SleepThread extends Thread{ @Override public void run(){ try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static class BusyThread extends Thread{ @Override public void run(){ while (true){ } } } }
运行结果:
busyThread isInterrupted=true busyThread isInterrupted=true sleepThread isInterrupted=false sleepThread isInterrupted=false main interrupted=true main interrupted=false java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at JavaMultiThreadProgramming.InterruptThread$SleepThread.run(InterruptThread.java:31)
结果分析:
busyThread线程与main线程结果对比验证:
busyThread线程中断之后,调用Thread的对象方法isInterrupted()获取busyThread线程的中断状态为true,再次调用还是为true,说明isInterrupted()方法不会重置中断标志位。
main线程中断之后,调用Thread.interrupted()方法,第一次返回true,第二次返回false,验证第一次调用Thread.interrupted()方法之后会重置该线程的中断标志位,因此第二次调用的时候返回结果为false.
sleepThread线程与busyThread线程结果对比:
busyThread线程一直在运行,中断之后,线程正常中断,而sleepThread线程一直在睡眠,中断之后会抛出InterruptedException异常;
sleepThread线程中断之后调用interrupted()方法,两次都返回false,验证在该线程正在sleep()的时候中断线程,在抛出异常之前会先清除中断标志位,即中断标识置为false。
静态方法
currentThread()方法
currentThread()方法返回代码段正在被哪个线程调用。静态方法。JDK中实现:
public static native Thread currentThread();
举例说明:
public class Run1 { public static void main(String[]args){ System.out.println(Thread.currentThread().getName()); } }
运行结果:
main
结果说明:main()方法被名为main的线程调用。
再次举例说明:
public class Run1 extends Thread{ public Run1(){ System.out.println("Run1 printed by "+Thread.currentThread().getName()); } @Override public void run(){ System.out.println("run printed by "+Thread.currentThread().getName()); } public static void main(String[]args){ System.out.println("main printed by "+Thread.currentThread().getName()); Run1 run1=new Run1(); run1.start(); //run1.run(); } }
运行结果:
main printed by main Run1 printed by main run printed by Thread-0
结果说明:
Run1的构造方法被main线程调用,run方法被thread-0的线程调用,说明run1.start()方法会新启一个线程。将上述代码中的run1.run()的注释取消,再次执行,得到的结果如下:
main printed by main Run1 printed by main run printed by main run printed by Thread-0
从上面的结果可以看出,直接调用线程的run方法run1.run(),该方法是由main线程来执行的,并没有新启一个线程。
sleep()方法
sleep()方法是让当前线程“正在执行的线程”休眠(暂停)指定的毫秒数。“正在执行的线程”指的是this.currentThread()返回的线程。JDK实现:
public static native void sleep(long millis) throws InterruptedException;
方法说明:
暂停线程,线程进入到waiting状态,会让出CPU。
不会放弃对象锁。
静态方法,作用在当前线程上。
举例说明:
public class Run3 extends Thread { @Override public void run(){ try { System.out.println("run threadName="+this.currentThread().getName()+" begin "+System.currentTimeMillis()); Thread.sleep(2000); System.out.println("run threadName="+this.currentThread().getName()+" end "+System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[]args) throws InterruptedException { Run3 run3=new Run3(); run3.start(); System.out.println("main "+" begin "+System.currentTimeMillis()); System.out.println("main "+" end "+System.currentTimeMillis()); } }
运行结果:
main begin 1515381264226 main end 1515381264226 run threadName=Thread-0 begin 1515381264226 run threadName=Thread-0 end 1515381266219
结果说明:因为main线程与Run3线程是异步执行的,所以main线程可能先执行完,打印了main的begin和end,Run3线程随后执行,打印run begin和run end.
yield()方法
yield()方法让当前线程放弃当前的CPU资源,将CPU让给其他的任务去占用CPU执行时间。由running状态变为ready状态。静态方法。但是放弃的时间不确定,有可能刚刚放弃,马上又获得了CPU时间片。JDK实现:
public static native void yield();
举例说明:
public class YieldThread extends Thread{ @Override public void run(){ long begin=System.currentTimeMillis(); long sum=0; for(int i=0;i<50000000;i++){ //Thread.yield(); sum+=i; } long end=System.currentTimeMillis(); System.out.println("sum =" +sum); System.out.println("sum cost time " +(end-begin)+" ms."); } public static void main(String[]args){ YieldThread thread=new YieldThread(); thread.start(); } }
运行结果:
sum =1249999975000000 sum cost time 47 ms.
取消掉上述代码注释后运行结果:
sum =1249999975000000 sum cost time 10332 ms
对象方法
isAlive()方法
isAlive()方法是判断当前线程是否处于活动状态。活动状态是线程已经启动且尚未终止。在JDK中实现如下:
public final native boolean isAlive();
举例说明:
public class Run2 extends Thread { @Override public void run(){ System.out.println("run = "+this.isAlive()); } public static void main(String[]args) throws InterruptedException { Run2 run2=new Run2(); System.out.println("begin == "+run2.isAlive()); run2.start(); System.out.println("end == "+run2.isAlive()); Thread.sleep(1000); System.out.println("Thread end == "+run2.isAlive()); } }
运行结果:
begin == false end == true run = true Thread end == false
上述结果说明:在线程run2没有执行start()方法的时候,线程还未运行,isAlive()=false,在执行start()之后,这里的结果其实是不确定的,end == true说明该线程还没有运行完成,因此isAlive=true,在睡眠1s之后,Thread end == false说明线程已经运行完成,所以isAlive==false.
join()方法
方法名称 | 方法说明 |
---|---|
public final synchronized void join(long millis); public final void join(); public final synchronized void join(long millis, int nanos); | 如果线程A执行了thread.join()语句,则当前线程A等待thread线程终止之后才从thread.join()返回。两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从超时方法中返回 |
///此处A timeout of 0 means to wait forever 字面意思是永远等待,其实是等到t结束后。 public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) {//判断线程是否处于活动状态 wait(0);//释放锁 } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
从源码可以看出:如果线程被生成了,但还未被起动,isAlive()将返回false,调用它的join()方法是没有作用的。将直接继续向下执行。
当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁。
举例说明:
public class JoinThread { public static void main(String[]args){ Thread previous=Thread.currentThread(); for(int i=0;i<10;i++){ Thread thread=new Thread(new Domino(previous),String.valueOf(i)); thread.start(); previous=thread; } } public static class Domino implements Runnable{ private Thread thread; public Domino(Thread thread){ this.thread=thread; } @Override public void run() { try{ thread.join(); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName()+" terminate."); } } }
运行结果:
0 terminate. 1 terminate. 2 terminate. 3 terminate. 4 terminate. 5 terminate. 6 terminate. 7 terminate. 8 terminate. 9 terminate.
join(long)方法与sleep(long)方法的区别
两个方法的区别主要是在对同步的处理上。方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)具有释放对象锁的特点。从源码中可以看出,当执行wait(long)方法后,当前线程的锁被释放,那么其他线程就可以调用此线程的同步方法了。而Thread.sleep(long)方法却不释放锁。
停止线程
在介绍如何安全的停止线程之前,先介绍3个不好的方法,在这里只说明不好的方法的缺点,不详细举例说明。stop()方法-已作废,强制停止线程
stop()方法在终结一个线程时不会保证线程的资源正常释放,没有给予线程完成资源释放工作的机会,可能会导致程序工作在不确定的状态下。
suspend()方法-已作废,暂停线程
resume()方法-已作废,恢复线程
suspend()/resume()方法在调用后,线程不会释放已经占有的资源(比如锁),而是占着资源进入睡眠状态,这样荣引起死锁问题。还容易出现因为线程的暂停而导致数据不同步的情况。
安全的终止线程
中断法+boolean变量法
通过这两种方式能够使线程咋终止时有机会去清理资源,而不是武断 地将线程停止,这两种方式使得线程的终止显得更加安全和优雅。实例如下:
public class Shutdown { public static void main(String[]args) throws InterruptedException { //中断法 Runner1 one=new Runner1(); Thread countThread=new Thread(one,"CountThread"); countThread.start(); TimeUnit.SECONDS.sleep(1); countThread.interrupt(); ////boolean变量法 Runner2 two=new Runner2(); countThread=new Thread(two,"CountThread"); countThread.start(); TimeUnit.SECONDS.sleep(1); two.cancel(); } private static class Runner1 implements Runnable{ private long i; @Override public void run() { while (!Thread.currentThread().isInterrupted()){ i++; } System.out.println("count="+i); } } private static class Runner2 implements Runnable{ private long i; private volatile boolean on=true; @Override public void run() { while (on){ i++; } System.out.println("count="+i); } public void cancel(){ on=false; } } }
运行结果:
count=24098615 count=511335299
从上面的结果看出,run()方法里面,while循环之外,还是会继续运行,打印count还是能够打印出来。
为了解决上述问题,还有一种方式来终止线程。
抛出异常法+return法
public class Shutdown2 { public static void main(String[]args) throws InterruptedException { Runner1 one=new Runner1(); Thread countThread=new Thread(one,"CountThread"); countThread.start(); TimeUnit.SECONDS.sleep(1); countThread.interrupt(); } private static class Runner1 implements Runnable{ private long i=0; @Override public void run() { try { while (!Thread.currentThread().isInterrupted()){ System.out.println("i="+(i++)); } if(Thread.currentThread().isInterrupted()){ System.out.println("Thread end!!!"); // throw new InterruptedException(); //return; } System.out.println("below for"); } catch (InterruptedException e) { System.out.println("in catch"); e.printStackTrace(); } } } }
运行结果(注释 // throw new InterruptedException();):
.................. i=174485 i=174486 i=174487 i=174488 i=174489 i=174490 Thread end!!! below for
运行结果(去掉注释 // throw new InterruptedException()):
i=168998 i=168999 i=169000 i=169001 Thread end!!! in catch java.lang.InterruptedException at Thread.Shutdown2$Runner1.run(Shutdown2.java:23) at java.lang.Thread.run(Thread.java:748) Process finished with exit code 0
结果分析:用抛出异常的方式,没有打印出”below for”.
return法
上述代码,注释掉// throw new InterruptedException();取消注释return;运行结果:
i=164759 i=164760 i=164761 i=164762 Thread end!!!
结果分析:同样没有”below for”.
线程优先级
在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。设置线程优先级有助于帮“线程规划器”确定下一次选择哪个线程来优先执行。设置线程的优先级使用setPriority(int)方法,默认优先级是5.在Java中,线程的优先级分为1~10这10个等级,如果小于1或者大于10,则JDK抛出异常throw new IllegalArgumentException()。设置优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。
JDK中使用3个常量来预置定义优先级的值。如下:
/** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10;
线程优先级的特性:
继承性
在Java中,线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A是一样的。
规则性
线程的优先级具有一定的规则性,也就是CPU尽量将执行资源让给优先级比较高的线程。
随机性
线程的优先级具有随机性,也就是优先级较高的线程不一定每次都先执行完。当有大量优先级高的线程和大量优先级低的线程一起执行的时候,呈现出来的是大多数的优先级高的线程先执行完,但是不是每一个高优先级的线程都比低优先级的线程先执行完。
守护线程(Daemon线程)
在Java线程中有两种线程,一种是用户线程,一种是守护线程。Daemon线程是一种支持性线程,因为他主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。Daemon属性需要在启动线程之前设置,不能在启动之后设置。
典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程也就没有存在的必要了,自动销毁。Daemon线程的作用是为其他线程的运行提供便利服务。
Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块并不一定会执行,实例如下:
public class DaemonThread extends Thread{ public void run(){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("DaemonThread finally run."); } } public static void main(String[]args){ DaemonThread thread=new DaemonThread(); thread.setName("DaemonThread"); thread.setDaemon(true); thread.start(); } }
运行结果:无
结果说明:
运行上述程序,可以看到在中断或者命令提示符上没有任何输出。main线程(非Daemon线程)在启动了DaemonThread之后随着main方法执行完毕而终止,而此时Java虚拟机中已经没有非Daemon线程,虚拟机需要退出。Java虚拟机中的所有Daemon线程都需要立即终止,因此DaemonThread立即终止,但是DaemonThread中的finally块并没有执行。在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。
线程的状态(线程的生命周期)
Java线程在运行的生命周期中有6种状态,在给定一个时刻,线程只能处于其中的一个状态。状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还没有调用start()方法 |
RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称为“运行中” |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,标识线程进入等待状态,进入该状态标识当前线程需要等待其他线程做出一些特定动作(通知或中断) |
TIME_WAITING | 超时等待状态,该状态不同于WAITING,它可以在指定的时间自行返回的 |
TERMINATED | 终止状态,标识当前线程已经执行完毕 |
1. 初始状态:new
创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
2. 运行态:runnable
在java中,运行态包含就绪态和运行态。
就绪态
该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就可以运行。
所有就绪状态的线程存放在就绪队列中。
运行态
获得CPU执行权,正在执行的线程。
由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程。
3.阻塞态:blocked
当一条正在执行的线程请求你某一资源失败时,就会进入阻塞态。
在Java中,阻塞态专指请求锁失败时进入的状态。(进入synchronized关键字修饰的方法或代码块时,获取锁失败)
由一个阻塞队列存放所有阻塞态的线程。
处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待 执行。
4.等待态:waiting
当前线程调用wait/join/park函数时,当前线程就会进入等待态。
也有一个等待队列存放所有等待态的线程。
线程处于等待态标识它需要等待其他线程的指示才能继续运行。
进入等待态的线程会释放CPU执行权,并释放资源(如锁)。
5.超时等待态:timed_waiting
当运行中的线程调用sleep(time)/wait/join/parkNanos/parkUntil时,就会进入该状态。
它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒。
进入该状态后会释放CPU执行权和占有的资源。
与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
6.终止态:terminated
线程执行结束后的状态。
阻塞在java.concurrent包中的Lock接口的线程是等待状态,因为java.concurrent包中的Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。
Java线程各状态之间的转换如下图所示:
![](http://upload-images.jianshu.io/upload_images/9953255-25118ccd0d9d4eb5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
参考:
Java多线程编程核心技术
Java并发编程的的艺术
该文是我的读书笔记,对知识进行总结,帮助自己以后回顾。
相关文章推荐
- java 多线程系列基础篇(五)之线程等待与唤醒
- java 多线程系列基础篇(六)之线程让步
- java 多线程系列基础篇(九)之interrupt()和线程终止方式
- 多线程系列一:线程基础
- Java多线程系列--“基础篇”06之 线程让步
- Java多线程系列--【基础篇07】- 线程休眠
- java 多线程系列基础篇(十)之线程优先级和守护线程
- javaSE_8系列博客——重要的基础Java类——多线程--2--进程和线程
- 多线程系列 - 基础篇01 - 线程基本概念 & 线程优先级 & 守护线程 60%
- java 多线程系列基础篇(七)之线程休眠
- java多线程基础(2)-调度方式之暂停当前线程方式1-阻塞
- java多线程基础(3)-调度方式之暂停当前线程方式2-放弃
- 多线程系列(二):多线程基础
- linux_c 开发(6-1)多线程程序设计_线程基础
- JAVA基础知识系列---线程与进程
- Java 多线程、并发系列之线程定义和线程状态
- Java基础学习__多线程(线程间通信--生产者消费者JDK5.0升级版)
- java多线程系列8-线程的优先级
- java多线程系列----------- 基本的线程机制(二)
- java多线程系列01——多线程基础