4.1线程基础
4.1.1 进程和线程
-
什么是线程
进程可以被看作程序的实体;同样,它也是线程的容器。
-
什么是线程
windows任务管理器一个进程下面有很多线程,线程是操作系统调度的最小单位,也叫作轻量级进程。在一个进程中可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能过访问共享的内存变量。
-
为何使用多线程
-
使用线程减少程序的响应时间
-
与进程相比,线程创建和切换的开销更小,同时多线程在数据共享方面的效率非常高。
-
多CPU 或者多核的计算机本身就具备执行多线程的能力。
-
使用多线程能简化程序的结构,使程序便于理解和维护。
-
4.1.2 线程的状态
Java线程在运行的生命周期中可能处于 6 中不同的状态,这 6 中不同线程如下所示。
-
New:新创建状态,线程被创建,还没有调用 start 方法,在线程运行之前还有一些基础工作要做。
-
Runable:可运行状态。一旦调用 start 方法,线程就处于 Runnable 状态。一个可运行的线程可能正在运行,也可能没有运行,这取决于操作系统线程提供运行的时间。
-
Blocked:阻塞状态。表示线程被锁阻塞,它暂时不活动。
-
Waiting:等待状态。线程暂时不活动,并且不运行任何代码,这消耗最少的资源,直到线程调度重新激活它。
-
Timed waiting:超时等待状态。和等待状态不同的是,它是可以在指定的时间自行返回的。
-
Terminated:终止状态。表示当前线程已经执行完毕。导致线程终止有两种情况:第一种就是 run 方法执行完毕正常退出;第二种就是因为一个没有捕获的异常而终止了 run 方法,导致线程进入终止状态。
4.1.3 创建线程
-
继承 Thread 类,重写 run 方法
public class TestThread extends Thread { @Override public void run() { super.run(); System.out.println("Hello World"); } }
TestThread testThread = new TestThread(); testThread.start();
-
实现 Runnbale 接口,并实现该接口的 run 方法
public class TestRunnable implements Runnable { @Override public void run() { System.out.println("Hello World"); } }
TestRunnable testRunnable = new TestRunnable(); Thread thread = new Thread(testRunnable); thread.start();
-
实现Callable 接口,重写 call 方法
Callable 接口实现是属于 Executor 框架中的功能类, Callable 接口与 Runable 接口的功能类似,但提供了比 Runable 接口更强大的功能,主要表现以下 3 点:
-
Callable 可以在任务接收后提供一个返回值,Runable无法提供这个功能。
-
Callable 中的 call 方法可以抛出异常,而 Runable 的 run 方法不能抛出异常。
-
运行 Callable 可以拿到一个 Futrue 对象: Future 对象表示异步计算的结果,他提供了检测计算是否完成的方法。由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下就可以使用 Furture 来监视目标线程调用 call 方法的情况。但调用 Future 的 get 方法以获取结果时,当前线程就会被阻塞,直到 call 方法返回结果。
public class TestCallable implements Callable { @Override public String call() throws Exception { return "Hello word"; } }
TestCallable testCallable = new TestCallable(); ExecutorService executorService = Executors.newSingleThreadExecutor(); Future future = executorService.submit(testCallable); try { System.out.println(future.get()); } catch (Exception e) { e.printStackTrace(); }
-
4.1.4 理解中断
当一个线程调用 interrupt
方法时,线程的中断标识为将被置位(中断标识为true),线程会不时地检测这个中断标识位,以判断线程是否应该被中断。要想知道线程是否被置位,可以调用 Thread.currentThread().isInterrupted()
,如下所示
while(!Thread.currentThread()).isInterrupted()){
// do something
}
还可以调用 Thread.interrupted()
来对中断标识位进行复位。但是如果一个线程被阻塞,就无法检测中断状态。如果一个线程处于阻塞,就无法检测中断状态。如果一个线程处于阻塞状态,那么线程在检查中断标识位时若发现中断标识位为true,则会在阻塞方法调用处抛出 InterruptedException
异常,并且在抛出异常前将线程的中断标识位复位,即重新设置为 false
。需要注意的是被中断的线程不一定会终止,中断线程是为了引起线程的注意,被中断的线程就可以决定如何去响应中断。如果是比较重要的线程,则不会理会中断;而大部分情况是线程将中断作为一个终止的请求。另外,不要在底层代码里捕获 InterruptedException
异常后不做处理,如下所示:
void myTask(){
...
try{
sleep(50)
}catch(InterruptedException e){
}
}
如果你不知道抛出 InterruptedException
异常后如何处理,这里介绍两种合理的处理方式。
(1)在 catch
子句中,调用 Thread.currentThread.interrupt()
来设置中断状态(因为抛出异常后中断标识为会复位),让外界通过判断 Thread.currentThread().isInterrupted()
来决定是终止线程还是继续下去,应该这样做:
void myTask(){
...
try{
sleep(50)
}catch(InterrupedException e){
Thread.currentThread().interrupt();
}
...
}
(2)更好的做法就是,不使用 try 来捕获这样的异常,让方法直接抛出,这样调用者可以捕获这个异常,如下所示:
void myTask() throw InterruptedException{
sleep(50)
}
4.1.5 安全的中断异常
1.用中断来终止线程
public class StopThread {
public static void main(String[] args) throws InterruptedException {
// 1.用中断来终止线程
MoonRunner runner = new MoonRunner();
Thread thread = new Thread(runner, "MoonThread");
thread.start();
TimeUnit.MILLISECONDS.sleep(10);
thread.interrupt();
}
}
public class MoonRunner implements Runnable {
private long i;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
i++;
System.out.println("i=" + i);
}
System.out.println("stop");
}
}
2.使用 boolean 值终止
public class StopThread2 {
public static void main(String[] args) throws InterruptedException {
MoonRunner2 moonRunner2 = new MoonRunner2();
Thread thread = new Thread(moonRunner2, "MoonThread");
thread.start();
TimeUnit.MILLISECONDS.sleep(10);
moonRunner2.cancle();
}
}
public class MoonRunner2 implements Runnable {
private long i;
private volatile boolean on = true;
@Override
public void run() {
while (on) {
i++;
System.out.println("i=" + i);
}
System.out.println("stop");
}
public void cancle() {
on = false;
}
}