什么是线程
现代操作系统调度的最小单元是线程,也叫轻量级进程,在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。
为什么要使用多线程
(1)更多的处理器核心
(2)更快的响应时间
(3)更好的编程模型
线程生命周期
线程创建之后,调用start()方法开始运行。当线程执行wait()方法之后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知(notify)才能够返回到运行状态,超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回到运行状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态。线程在执行Runnable的run()方法完之后将会进入到终止状态。
阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
但利用Lock接口进行同步的线程状态却是等待状态,因为Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法而不是synchronized。
线程的几种创建模式
通过实现Runnable接口来创建Thread线程:
public class Main {
public static void main(String[] args) {
MyThread task = new MyThread();
Thread thread = new Thread(task);
thread.start();
}
static class MyThread implements Runnable {
@Override
public void run() {
while (true){
System.out.println("MyThread is ruing");
}
}
}
}
控制台输出
MyThread is ruing
MyThread is ruing
MyThread is ruing
…
通过继承Thread类来创建一个线程:
public static void main(String[] args) {
MyThread2 thread = new MyThread2();
thread.start();
}
static class MyThread2 extends Thread {
@Override
public void run() {
while (true){
System.out.println("MyThread2 is ruing");
}
}
}
控制台输出
MyThread2 is ruing
MyThread2 is ruing
MyThread2 is ruing
…
通过实现Callable接口来创建Thread线程
public static void main(String[] args) {
FutureTask<Integer> future = new FutureTask(new MyCallable());
Thread thread = new Thread(future);
thread.start();
try {
/**
* get()返回Callable任务里的call()返回值
* get方法是一个阻塞方法,对于task内置了一些任务状态,当任务状态为新建0或者初始化完成的时候1的时候会阻塞
* 需要根据设置的时间阻塞,没有设置时会一直进行阻塞,一直到有结果返回
*/
Integer result = future.get();
System.out.println("线程运行结果"+result);
} catch (Exception e) {
e.printStackTrace();
}
}
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws InterruptedException {
int i = 0;
int sum = 0;
while(i<10){
Thread.sleep(100);
sum+=i;
System.out.println(i);
i++;
}
return sum;
}
}
控制台输出
0
1
2
3
4
5
6
7
8
9
线程运行结果45
创建线程的三种方式的对比
-
实现Runnable、Callable接口
优点:线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。可以多个线程可以共享同一个target对象,一起执行,适合多个线程来处理同一份资源的情况。 -
继承Thread类的
优点:编写简单,直接使用this即可获得当前线程。
缺点:线程类已经继承了Thread类,所以不能再继承其他父类。 -
Runnable和Callable的区别
(1) Callable重写的方法是call(),Runnable重写的方法是run()。
(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
(3) call方法可以抛出异常,run方法不可以。
(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
Future接口的结构
-
boolean cancel(boolean mayInterruptIfRunning);
尝试取消执行此任务。如果任务已经完成,已经被取消或其他原因而无法取消,则失败。
如果成功,并且此任务尚未开始,则此任务永远不会运行。
如果任务已经开始,则参数确定是否应中断执行该任务的线程以尝试停止该任务。
调用这个方法返回后,isDone方法将始终返回true。
如果此返回了true,则 isCancelled方法将始终返回true。 -
boolean isCancelled();
如果此任务在正常完成之前被取消,则返回true -
boolean isDone();
如果此任务完成,则返回 true。
由于正常终止,异常或取消引起的,此方法都将返回true -
V get() throws InterruptedException, ExecutionException;
等待必要的计算完成,然后检索其结果。 -
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
必要时最多等待给定时间以完成计算,然后检索其结果,如果超时则不等待。
守护线程
守护线程主要被用作程序中后台调度以及支持性工作。当一个Java虚拟机中不存在非守护线程的时候,Java虚拟机将会退出。
可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程(需要在启动线程之前设置,不能在启动线程之后设置)。
守护线程不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。因为当主线程结束后,守护线程也会被结束。
终止线程
中断
中断可以理解为线程的一个标识位属性,表示一个运行中的线程是否被其他线程进行了中断操作,其他线程通过调用该线程的interrupt()方法对其进行中断操作。
线程通过调用自身方法isInterrupted()来进行判断是否被中断。如果线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返回false。
线程要对中断做出响应才能合理利用中断
private static class Runner implements Runnable {
private long i;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
i++;
}
System.out.println("Count i = " + i);
//进行资源释放等操作
}
}
例如此时在其他线程调用此线程的interrupt(),那么在执行到Thread.currentThread().isInterrupted()跳出循环,释放资源后结束线程。
在终止线程时也不能直接武断的调用suspend()、resume()和stop()进行终止,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会。
**应该在任务处理时就设计好响应中断,利用中断来做终止线程的工作,当判断中断时就跳出循环,结束线程 **