第四章 Java并发编程基础
1基本概念
1.1什是线程?
线程是系统调度的最小单位。一个进程中可以包含多个线程;处理器会在线程上高速切换,就像是多个线程同时在执行。
1.2线程的特点
线程含有自己的计数器。堆栈、局部变量等属性,并能够访问共享的内存变量。
1.3为何使用多线程
- 硬件的快速发展:处理器的性能得到巨大的提升的同时,越来越多的PC采用了多核处理器。这样,每个线程平均能分配到的时间片就变多了,硬件的发展,提升了多线程的处理能力。
- 更快的响应时间:多线程的使用,多个线程几乎同时在执行,将任务分拆交由不同线程去执行,降低了运算时间,给用户带来更快的响应速度。
1.4线程优先级
现代才做系统大多数采用时分的形式调度线程。系统会分配时间片,每个线程都有机会被分配到若干时间片,时间片就是线程能够调用处理器资源的时间。线程优先级高的线程,将会有更多的机会分配到时间片。
Java使用priority来控制线程的优先级。可以使用setPriority(int)来控制优先级。其中,优先级的范围为1~10,默认优先级是5。
tips:操作系统不一定会理会Java对于优先级的设置,最终的线程优先级结果来自于操作系统自己的调度。
1.5线程的状态
一般操作系统中,线程状态有五种:
新建状态(New)
就绪状态(Runnable)
运行状态(Running)
阻塞状态(Blocked)
死亡状态(Dead)
Java中个线程定义了6种状态,分别是:
- NEW : 初始状态,线程被创建,但没有start;
- RUNNABLE : 运行状态(就绪状态——表示可以运行,但不一定运行,可能需要等待分配时间片、运行状态——当线程获得时间片后进入运行状态,真正开始执行run()方法),java将这两种状态合并成了一种状态;
- BLOCKED:阻塞状态,获取不到锁,被阻塞了;
- WAITING: 等待状态,需要其他线程来notify或者intercept;
- TIME_WAITING: 超时等待状态,与WAITING相比,它会在指定时间内自动返回;
- TERMINATED: 终止状态,表示线程执行完毕了。
1.6Daemon线程
Daemon线程是一种支持型线程,一般作为后台调度以及支持型工作。
设置线程为Deamon线程
setDaemon(boolean flag)
Thread deamonThread = new Thread(new Runnable() {
public void run() {
System.out.println("test1");
}
});
deamonThread.setDaemon(true);
tips:
当JVM中不存在非Daemon线程的时候,JVM将会退出。
这个就比较厉害了,也就是说,其实是存在某种情况下,某些代码可能无法执行。
2 中断Interrupt
2.1什么是中断
中断的意思不是停止(stop)一个线程,而是去“提醒”,并且采用了让这个线程抛出异常的方式去“提醒”这个线程——你该干嘛了。具体要让这个线程干嘛?这个不好说,不同的场景下,可能还不太不一样。
其中,要注意的是:通过一个线程去向另一个线程发出“提醒”信号,抛出异常的不是发出信号的线程,而是被通知的线程抛出了异常。
假设,线程a中:
new Thread(new Runnable() {
public void run() {
try {
while (true){
Thread.sleep(1000);
System.out.println("b");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
b.interrupt();
上述代码中,在a线程通知了b线程去中断,所以在b线程中抛出异常。
2.2什么时候抛中断异常
通知的时候,也不是马上就中断,并抛出异常。interrupt()只是将修改了中断标志位。
源码如下:
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
有趣的是,就算一个线程被调用了interrupt()方法,也不一定就会抛出InterruptedException,只有当这个线程调用了能够抛出InterruptedException的方法的时候才会抛出异常,比如sleep、join、wait,如果该线程没有调用这类方法,就算中断标志位被设置了,也不会抛出异常。
可以想象,在sleep、join、wait这些方法中,即使处于等待状态,也依然会去检测自身线程中断标志位状态,看看是否需要抛出异常。
2.2中断的意义
两个角度去看待:
- 中断可以唤醒一个线程:比如,一个处于wait状态的线程,通过中断,就可以让它重新苏醒过来。当然,醒来以后做什么事,由catch块来控制,然后,就继续一行一行往下执行代码罗~
- 可以通过中断来结束一个线程的任务。比如示例一:在一个runable内部有如下代码,如果让该线程interrupt,while循环就会被调出,也就结束了任务。
- 切换任务:如示例二,try块在while内部,此时在catch中做其他操作,其实可以切换工作,也可以做些别的。
示例一:
public void run() {
try {
while (true){
Thread.sleep(1000);
System.out.println("b");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
示例二:
public void run() {
while (true){
try {
Thread.sleep(1000);
System.out.println("b");
} catch (InterruptedException e) {
doOtherWork();
}
}
}
2.3如何判断线程是否被中断
用Thread.currentThread().isInterrupted()啦;
有个高仿的方法,叫做Thread.interrupted(),长得差不多,其实,它是重置标志位用的。
来看下源码:
public static boolean interrupted() {
//返回标志位,并重置标志位,第二次必返回false
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
源码长得也差不多,都调用了一个native方法isInterrupted(),isInterrupted只有一个参数ClearInterrupted,看名字就能get到——是否清除标志位。
2.4中断标志位什么时候清除
- 调用interrupted()后;
- 线程被interrupt,抛出异常之前,标志位就会被清除,所以,抛出异常后,若调用isInterrupted()来查询,会返回false;
2.5什么时候中断无效
当线程处于synchronized获取锁的过程中,是不会抛出异常的,中断无效。若此时死锁了,就永远也无法抛出了。
2.6一些使用场景
- 点击某个桌面应用中的取消按钮时;
- 某个操作超过了一定的执行时间限制需要中止时;
- 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
- 一组线程中的一个或多个出现错误导致整组都无法继续时;
- 当一个应用或服务需要停止时。
(使用场景参考了http://www.infoq.com/cn/articles/java-interrupt-mechanism )
3 暂停、继续、停止
suspend(),暂停
resume(),恢复线程、
stop(),停止线程
三个方法均已经过时了。原因是,suspend并不会释放线程所占有的资源,而是占着资源进入睡眠状态,容易发生死锁;而stop方法则直接一刀切,杀死线程,完全没有释放资源的机会。
其中,暂停、继续、可以用等待、通知机制来替代。
那么,如何停止线程呢?标准做法是:可以通过中断、或者是设置一个标志位来停止线程。
private static class stopThread extends Thread{
private boolean isRunning = true;
@Override
public void run() {
while(isRunning && !Thread.currentThread().isInterrupted()){
System.out.println("i am running~~~");
}
}
public void stopThread(){
this.isRunning = false;
}
}