并发编程基础_02

举报
kwan的解忧杂货铺 发表于 2024/08/07 21:25:16 2024/08/07
【摘要】 二.线程状态 1.线程的状态?状态名称说明NEW初始状态,线程被构建,但是还没有调用 start 方法RUNNABLE运行状态,Java 线程将操作系统中的就绪和运行两种状态笼统地称作“运行中”BLOCKED阻塞状态,表示线程阻塞于锁WAITING等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)TIME WAITING超时等待状态,该状态...

二.线程状态

1.线程的状态?

状态名称 说明
NEW 初始状态,线程被构建,但是还没有调用 start 方法
RUNNABLE 运行状态,Java 线程将操作系统中的就绪和运行两种状态笼统地称作“运行中”
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
TIME WAITING 超时等待状态,该状态不同于 WAITING,它是可以在指定的时间自行返回的
TERMINATED 终止状态,表示当前线程已经执行完毕
  • NEW :创建了线程对象但尚未调用 start()方法时的状态。
  • RUNNABLE:线程对象调用 start()方法后,线程处于可运行状态,此时线程等待获取 CPU 执行权。
  • BLOCKED:线程等待获取锁时的状态。
  • WAITING:线程处于等待状态,处于该状态标识当前线程需要等待其他线程做出一些特定的操作唤醒自己。
  • TIME_WAITING:超时等待状态,与 WAITING 不同,在等待指定的时间后会自行返回。
  • TERMINATED:终止状态,表示当前线程已执行完毕。

2.线程池的状态

线程池的状态:

image-20230727112857606

image-20230727112914578

3.线程状态变迁?

  • RUNNABLE 有 2 种状态,一种是 running,调用 yield 变为 ready,一种是 ready,调用 run 为 running
  • New 状态为初始状态,New Thread 的时候是 new 这种状态,调用 start 方法后,变为 runnable
  • Terminated 任务执行完成后线程的状态为终止
  • runnable 状态到 waiting 状态
    • wait()
    • join() 不需要主动唤醒
    • park()
  • waiting 状态到 runnable 状态
    • 唤醒
    • notify()
    • notifyAll()
    • unpark(thread)
  • runnable 状态到 timed_waiting 状态
    • sleep(long)
    • wait(long)
    • join(long) 不需要主动唤醒
    • parkNanos()
    • parkUntil()
  • timed_waiting 状态到 runnable 状态
    • notify()
    • notifyAll()
    • unpark(thread)
  • blocked 到 runnable
    • 获取到锁
  • runnable 到 blocked
    • synchronized 方法
    • synchronized 块

image-20220609152528859

4.CPU 术语定义?

术语 英文单词 描述
内存屏障 memory barriers 是一组处理器指令,用于实现对内存操作的顺序限制
缓冲行 cache line 缓存中可以分配的最小存储单位。处理器填写缓存线时会加载整个缓存线,需要使用多个主内存读周期
原子操作 atomic operations 不可中断的一个或一系列操作
缓存行填充 cache line fill 当处理器识别到从内存中读取操作数是可缓存的,处理器读取整个缓存行到适当的缓存(L1L2.L3 的或所有)
缓存命中 cache hit 如果进行高速缓存行填充操作的内存位置仍然是下次处理器访问的地址时,处理器从缓存中读取操作数,而不是从内存读取
写命中 write hit 当处理器将操作数写回到一个内存缓存的区域时,它首先会检查这个缓存的内存地址是否在缓存行中,如果存在一个有效的缓存行,则处理器将这个操作数写回到缓存,而不是写回到内存,这个操作被称为写命中
写缺失 write misses the cache 一个有效的缓存行被写人到不存在的内存区域

5.java 内存模型?

JMM内存模型中定义了 8 种指令操作,这 8 种操作具有原子性。

  • lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程的独占状态。
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 操作使用。(工作在主存,传输数据到从存)
  • load(载入):作用于工作内存的变量,它把 read 操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将执行该操作。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行该操作。
  • store(存储):作用于工作内存的变量,它把工作内存中的一个变量的值传递到主内存中,以便随后的 write 操作使用。(工作在从存,传输数据到主存)
  • write(写入):作用于主内存的变量,它把 store 操作从工作内存中得到的变量值放入主内存的变量中(更新操作)。

:从主内存到工作内存,需顺序执行 read 和 load,从工作内存到主内存,需顺序执行 store 和 wirte 操作,JMM 只要求这些操作是顺序执行的,并不保证连续执行。

Java 线程之间的通信由 Java 内存模型(本文简称为 JMM)控制,JMM 决定一个线程对共享变量的写入何时对另一个线程可见。

JMM 关于同步的规定:

  1. 线程解锁前,必须把共亨变量的值刷新回主内存
  2. 线程加锁前,必须读取主内存的最新值到自己的工作内存
  3. 加锁解锁是同一把锁

image-20240126151413556

6.什么是 Daemon 线程?

Daemon 线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个 Java 虚拟机中不存在非 Daemon 线程的时候,Java 虚拟机将会退出。可以通过调用 Thread.setDaemon(true)将线程设置为 Daemon 线程。注意 Daemon 属性需要在启动线程之前设置,不能在启动线程之后设置。Daemon 线程被用作完成支持性工作,但是在 Java 虚拟机退出时 Daemon 线程中的 fnally 块并不一定会执行.

public class Daemon{
  public static void main(String[] args){
    Thread thread=new Thread(new DaemonRunner()"DaemonRunner");
    thread.setDaemon(true);
    thread.start();
  }
  static class DaemonRunner implements Runnable{
    @Override
    public void run() {
      try{
        SleepUtilssecond(10);
      } finally{
        System.out.println("DaemonThread finally run.");
      }
    }
  }
}
  • 支持型线程
  • 守护线程
  • finally 里面的代码不一定会执行
public final void setDaemon(boolean on) {
  checkAccess();
  if (isAlive()) {
    //告诉我们,必须要先设置线程是否为守护线程,
    //然后再调用start方法。如果你先调用start,后设置守护线程,则会抛出异常
    throw new IllegalThreadStateException();
  }
  daemon = on;
}

7.说说三个中断方法?

说说 interrupt(),interrupted(),isInterrupted()的区别

interrupt()、interrupted()和 isInterrupted()都是 Java 中与线程中断相关的方法,但它们的含义和用法有所不同。

interrupt()方法:

interrupt()方法是一个实例方法,用于中断当前线程或指定线程。当该线程处于阻塞状态(如 sleep、wait、join 等方法)时,会抛出 InterruptedException 异常,从而使线程退出阻塞状态。当该线程处于非阻塞状态时,会将线程的中断状态设置为 true,但并不会中断线程的执行。该方法并不会直接中止线程的执行,而是设置线程的中断标志位,需要用户自行检查标志位并处理中断操作。

interrupted()方法:

interrupted()方法是一个静态方法,可以用于检查当前线程是否被中断,并清除中断状态。如果线程被中断,该方法会返回 true,同时将线程的中断状态设置为 false。该方法会清除当前线程的中断状态,因此如果希望保留中断状态,可以使用 isInterrupted()方法。

isInterrupted()方法:

isInterrupted()方法是一个实例方法,用于检查当前线程或指定线程的中断状态。如果线程被中断,该方法会返回 true,否则返回 false。该方法不会清除线程的中断状态,因此可以用于在判断中断状态的同时保留中断状态。

需要注意的是,interrupted()方法和 isInterrupted()方法都是用于检查中断状态的方法,但它们的使用场景略有不同。interrupted()方法是静态方法,用于检查当前线程的中断状态,并清除中断状态,因此只能用于检查当前线程的中断状态。而 isInterrupted()方法是实例方法,可以用于检查当前线程或指定线程的中断状态,并且不会清除中断状态,因此可以用于检查线程的中断状态而不影响后续的中断处理。

方法传参:true 代表清除中断标志,false 代表不清除中断标志

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;
    }
  }
  interrupted();
}

public static boolean interrupted(){
  return currentThread().isInterrupted(true);
}

public boolean isInterrupted(){
  return isInterrupted(false);
}

private native boolean isInterrupted(boolean ClearInterrupted);

8.说说 join 方法和 yeild 方法?

join 方法和 yield 方法都是 Java 中与线程控制相关的方法,但它们的含义和用法有所不同。

join方法:

join()方法是一个实例方法,用于等待指定线程的执行完成。当线程 A 调用线程 B 的 join()方法时,线程 A 会被阻塞,直到线程 B 执行完成或超时。join()方法可以用于等待某个线程执行完成,然后再继续执行后续操作,常用于线程之间的协作。

yield方法:

yield()方法是一个静态方法,用于提示线程调度器当前线程愿意放弃当前的 CPU 资源,让其他线程执行。调用 yield()方法并不会让线程进入阻塞状态,当一个线程调用 yield 方法时,当前线程会让出 CPU 使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出 CPU 的那个线程来获取 CPU 执行权。

9.说说 sleep 和 yeild 和 wait 区别?

  • sleep 方法与 yeild 方法的区别在于,当线程调用 sleep 方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程。不会释放资源锁,只是让出 CPU 的时间片.线程会阻塞.

  • sleep 方法不释放锁,释放 cpu,可响应中断.先清除中断标志,再打印异常

  • sleep 必须捕获异常,而 wait,notify 和 notifyAll 不需要捕获异常

  • wait,notify 和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用

  • 调用 yield 方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度时就有可能调度到当前线程执行。

  • wait 方法会阻塞,会释放锁,会让出 CPU 的使用权,且不会参与锁竞争,除非被唤醒后才参与竞争.

10.说说 notify 和 wait 方法?

一个线程调用共享对象的 notify()方法后,会唤醒一个在该共享变量上调用 wait()方法后被挂起的线程。此外,被唤醒的线程不能马上从 wait 方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回(调用 wait 方法后,会释放当前共享对象的锁,如果不释放会造成死锁),也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会立即获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行。

类似 wait 系列方法,只有当前线程获取到了共享变量的监视器锁后,才可以调用共享变量的 notify()方法,否则会抛出 IllegaMonitorStateException 异常。

  • Thread 类的方法:sleep(),yield()
  • Object 的方法:wait()和 notify()、notifyAll()
  • Condition 接口中的方法: await()、signal()、signalAll()

线程阻塞和唤醒方法对比:

wait()、notify()、notifyAll() 这三个方法是 Object 超类中的方法.

await()、signal()、signalAll() 这三个方法是 Lock 的 Condition 中的方法.

11.说说对象的监视器锁?

一个线程如何才能获取到对象的监视器锁呢

  • 执行 synchronized 同步代码块时,使用该共享变量作为参数。
synchronized (MonitorTest.class){
//do something
}
  • 调用该共享变量的方法,并且该方法使用了 synchronized 修饰。
synchronized void add(int a){
//do something
}

12.直接调用 wait 方法?

会出现 IllegalMonitorStateException 异常,出现的原因如下:

如果调用 wait()方法的线程没有先获取该对象的监视器锁,则调用 wait 方法时调用线程会抛出 IllegalMonitorState Exception 异常。Object.notify(), Object.notifyAll(), Object.wait(), Object.wait(long), Object.wait(long, int)都会存在这个问题

public class ExceptionTest {
  public static void main(String[] args){
    Object object = new Object();
    try {
      object.wait();
    } catch (InterruptedException e){
      e.printStackTrace();
    }
  }
}

13.什么是虚假唤醒?

什么是虚假唤醒?如何避免虚假唤醒?

在一个线程没有被其他线程调用 notify()、 notifyAll()方法进行通知,或者被中断,或者等待超时,这个线程仍然可以从挂起状态变为可以运行状态(也就是被唤醒),这就是所谓的虚假唤醒。

虽然虚假唤醒在应用实践中很少发生,但要防患于未然,做法就是不停地去测试该线程被唤醒的条件是否满足,不满足则继续等待,也就是说在一个 while 循环中调用 wait()方法进行防范.退出循环的条件是满足了唤醒该线程的条件。

synchronized (obj){
  //do something
  while (条件不满足){
    obj.wait();
  }
}

While 在这里是防止虚假唤醒的关键,试想下,一旦发生虚假唤醒,线程会根据 while 添加再次进行判断,一旦条件不满足,会立即再次将线程挂起.

14.线程优先级?

优先级是针对 CPU 的时间片的长短,不是先后.

prio=5 和 os_prio=0
默认优先级为 5,os_prio 是 jvm 相对于 os 的优先级,优先级的数值在 1~10,越大优先级越高
setPriority 这个方法,他是 jvm 提供的一个方法,并且能够调用本地方法 setPriority().我们发现优先级貌似没有起作用,为什么?

1.我们现在的计算机都是多核的,t1,t2 会让哪个 cpu 处理不好说。由不同的 cpu 同时提供资源执行。

2.优先级不代表先后顺序。哪怕你的优先级低,也是有可能先拿到我们的 cpu 时间片的,只不过这个时间片比高优先级的线程的时间片短。优先级针对的是 cpu 时间片的长短问题。

3.目前工作中,实际项目里,不必要使用 setPriority 方法。我们现在都是用 hystrix,sential 也好,一些开源的信号量控制工具,都能够实现线程资源的合理调度。这个 setPriority 方法,很难控制。实际的运行环境太复杂。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。