一、多线程详解
1、什么是线程
线程是一个操作系统概念。操作系统负责这个线程的创建、挂起、运行、阻塞和终结操作。而操作系统创建线程、切换线程状态、终结线程都要进行CPU调度——这是一个耗费时间和系统资源的事情。
腾讯认证T9后端开发岗位,linux服务器开发高级架构师系统学习视频点击:C/C++Linux服务器开发高级架构师/Linux后台架构师
B站7000+播放的线程池视频讲解:150行代码,手写线程池(完整版)
2、线程生命周期
线程通常都有五种状态,创建、就绪、运行、阻塞和死亡:
- 创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
- 就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
- 运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
- 阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
- 死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。
可以用过jstack 或者idea debug快照显示状态,常见名词大致意思为:
- "Low Memory Detector":负责对可使用内存进行检测,如果发现可用内存低,分配新的内存空间。
- "CompilerThread0":用来调用JITing,实时编译装卸class。
- "Signal Dispatcher":负责分发内部事件。
- "Finalizer":负责调用Finalizer方法。
- "Reference Handler":负责处理引用。
- "main":是主线程。
- "VM Thread", "VM Periodic Task Thread":从名字上看是虚机内部线程。
3、线程状态描述
- NEW:状态是指线程刚创建, 尚未启动。
- RUNNABLE:状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等
- BLOCKED:这个状态下, 是在多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法, 也就是这里是线程在等待进入临界区
- WAITING:这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一般该线程可以继续下一步操作
- TIMED_WAITING: 这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态
- TERMINATED:这个状态下表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)
要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束。核心区别就是BLOCKED没拿到锁,WAITING拿到了锁。
4、线程优先级Priority
线程的优先级是将该线程的重要性传给了调度器、cpu处理线程顺序有一定的不确定,但是调度器会倾向于优先权高的先执行。
5、线程实现方式
线程有三种实现方式:Thread、Runnable、Callable。
Thread实现方式代码如下:
public class Thread01 extends Thread {
@Override
public void run() {
System.out.println("Thread 方式创建线程");
}
public static void main(String[] args) throws InterruptedException {
new Thread01().start();//多线程
}
}
Runnable实现方式:
public class Runnable01 implements Runnable {
@Override
public void run() {
System.out.println("Runnable方式创建线程");
}
public static void main(String[] args) {
new Thread(new Runnable01()).start();
}
}
Callable实现方式:
public class Callable01 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("Callable方式创建线程");
return "Callable";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask task=new FutureTask(new Callable01());//有参 赋值 成员属性
new Thread(task).start();
System.out.println( task.get());;
}
}
6、Thread和Runnable的联系与区别
- Runnable的实现方式是实现其接口即可。
- Thread的实现方式是继承其类。
- Runnable接口支持多继承,但基本上用不到。
- Thread实现了Runnable接口并进行了扩展,而Thread和Runnable的实质是实现的关系,不是同类东西,所以Runnable或Thread本身没有可比性。public class Thread implements Runnable { // 省略 @Override public void run() { if (target != null) { target.run(); } } // 省略 }
综上所述:Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。
7、Callable原理是什么
Callable 1.5引入,具有返回值,并且支持泛型:
public interface Callable<V> {
V call()