深入理解Java高并发编程(2) - 线程基础

1. 线程创建 & callable

线程创建的方法:

  • 直接通过Thread类创建线程。

    • 通过实例Thread类(匿名内部类),重写thread的run方法因为本身的run方法是没有功能上的实现的,相当于是构造了匿名内部类),创建线程。再通过start方法启动线程。
  • 通过runnable接口创建线程。

    • runnable接口是一个函数式接口,只有一个run方法,通过Thread t = new Thread(runnable)将runnable对象(lambda表达式或者匿名内部类)传给thread构造方法,本质上是thread对象会检查自己有没有runnable类型的属性(构造方法接受runnable对象,会把这个对象传给thread的target属性),而对于thread的run方法中,如果target不为空,就会调用target上的run方法,也就是传过来的lambda表达式的方法。
      image-20260309145523752
    • 这种方法通过把线程和任务分开,更加灵活。
  • FutureTask通过实现Runnabletask间接实现了runnable接口处理不带返回值的任务(runnable),除此之外,FutureTask还继承了Future接口间接实现了Callable接口,用于接受任务返回结果。FutureTask通过接收一个Callable类型(runnable的增强)的lambda表达式, 处理带返回值的任务

  FutureTask<Integer> task = new FutureTask(() -> return 1);
  Thread t = new thread(task);

Runnable vs Callable

  • callable 是带返回值的任务,而runnable不带返回值
  • runnable只能抛出非受检异常(runtimeException),而callable会抛出任意异常(后面线程池中有例子)
  • runnable可以直接传给thread执行,callable必须先包装成FutureTask再交给Thread或者直接通过线程池执行。
  • 当调用callable包装的futureTask的get方法时,需要等待方法的结果,会阻塞线程

2.线程运行原理

2.1 私有栈内存

jvm运行区内存中的栈是每个线程私有的,当每调用一个方法,都会有一个栈帧,栈顶的栈帧叫做活动栈帧,栈帧中会记录 局部变量表,

操作数栈,动态连接,返回地址,其中返回地址是记录上一个栈帧的地址,当活动栈帧的方法被执行完了就会返回到上一个栈帧的地址。

当所有的线程(非守护线程)执行结束时,程序也就执行结束了。

2.2 线程上下文切换

线程上下文切换指的是当前线程状态被保存,恢复被分配时间片的内存的状态的过程,线程上下文切换存在一定消耗。

以下情况会导致线程上下文切换

  • cpu时间片用完
  • gc
  • 更高优先级线程需要运行
  • 线程调用了sleep,yield,wait,join,park,synchronize,lock等方法

3. 线程方法

3.1 为什么不能用run方法代替 start方法

run方法声明了线程具体要执行的方法,start是当前线程启动另一个线程,如果直接在当前线程中调用run方法,效果就是当前线程执行了run方法,而不是start的新创建线程执行run方法,不能实现异步的效果。

3.2 interrupt 方法

interrupt方法可以打断阻塞状态/运行状态的线程,如调用了 sleep,wait,join的方法

  • 打断阻塞状态的线程会抛出一个IntegerruptedException,同时会有一个打断标记,如果打断 sleep,join,wait的线程,会把打断标记设置为false,通过异常的方式表示被打断了
  • 打断正常运行的线程不会立即让运行的线程停止,而是线程中通过判断打断标记来决定是不是要停下来

获取打断标记:

  • isInterrupted():非静态方法,判断是否被打断,不会清除打断标记
  • Interrupted()静态方法,同样是判断是否被打断,但是会将打断标记重新设置为false

interrupt打断park线程

当打断标记为true时,再执行park方法就不能让线程停下来了。

4. 守护线程

默认来说,Java进程需要等待所有线程都运行完毕再结束,守护线程是一种特殊线程,这种线程是特点是即使守护线程线程没运行完,非守护线程运行完了进程也会结束。

通过setDaemon()方法设置守护线程。

垃圾回收线程就是一种常见的守护线程。

5. 线程状态

5.1 操作系统层面的五种状态

  • 初始状态
    • 仅在语言层面创建了线程对象,还未与操作系统系统关联
  • 可运行状态
    • 线程被创建,且可以由cpu分配时间片调度执行
  • 运行状态
    • 获取cpu时间片运行中的线程,当时间片消耗尽会变为可运行状态,这时候会发生上下文的切换。
  • 阻塞状态
    • 调用阻塞API,如读写文件,这些线程实际是不会用到cpu的,也会发生上下文切换。
  • 终止状态
    • 线程已经执行完毕,生命周期结束,不会再进入其他状态。
      image-20260310132949160

5.2 Java语言层面上的6种状态

  • New
    • 线程对象刚刚被创建,还没有执行start方法
  • Runnable
    • 包括操作系统层面的运行,可运行,阻塞状态,也就是获取时间片,没获取时间片,读写IO造成的阻塞的情况(阻塞)
  • wait(Java中的阻塞,调度器不会分配时间片)
    • 没传时间参数的join,park出现的状态
  • TimedWaiting(Java中的阻塞,调度器不会分配时间片)
    • 一般是调用了传时间参数的sleep join 等方法时候出现的状态,park方法是没有时间参数的,parkNanos(long nanos)/parkUntil(long millis)能实现带超时的park,让线程进入TimedWaiting状态
  • block(Java中的阻塞,调度器不会分配时间片)
    • 竞争锁的时候就会出现block状态
  • terminated
posted @ 2026-04-08 14:51  不会coding的喵酱  阅读(7)  评论(0)    收藏  举报