Java 面试题总结之并发编程常见面试题

文章目录

  • 线程与进程的区别
  • 线程的生命周期
  • 线程的创建方式
  • 如何停止正在运行的线程
  • wait() 方法与 sleep() 方法的区别
  • 为什么需要把 wait()/notify() 放在 synchronized 代码块里?
  • start() 与 run() 方法的区别
  • ThreadLocal

并发编程面试文章地址链接

并发编程面试题之 volatile 关键字 https://blog.csdn.net/weixin_38251871/article/details/104667384 并发编程面试题之 CAS https://blog.csdn.net/weixin_38251871/article/details/104667406 并发编程面试题之锁 https://blog.csdn.net/weixin_38251871/article/details/104667392 并发编程面试题之阻塞队列 待完成… 并发编程面试题之 AQS 待完成… 并发编程面试题之线程池 https://blog.csdn.net/weixin_38251871/article/details/104675416 并发编程面试题之 synchronized 和 ReentrantLock 的区别 https://blog.csdn.net/weixin_38251871/article/details/104667532 并发编程面试题之 CyclicBarrier、CountDownLatch、Semaphore https://blog.csdn.net/weixin_38251871/article/details/104677582 并发编程面试题之 ConcurrentHashMap https://blog.csdn.net/weixin_38251871/article/details/104667433 并发编程面试题之 synchronized 实现原理 https://blog.csdn.net/weixin_38251871/article/details/104667415

线程与进程的区别

  • 进程是操作系统 分配资源 的最小单元,线程是操作系统 调度 的最小单元,也可以说是进程里的一个执行单元
  • 一个程序至少有一个进程,一个进程至少有一个线程

线程的生命周期

  • 新建【NEW】:当程序使用 new 关键字创建了一个 Thread 对象后,该线程就处于新建状态,此时的 JVM 为其分配内存,并且初始化其成员变量的值;例 Thread t1 = new Thread()
  • 就绪状态/可运行状态【RUNNABLE】:当线程对象调用 start() 方法后,该线程就处于可运行状态,此时的 JVM 会为其创建方法调用栈和程序计数器,等获取到 CPU 后执行
  • 运行状态【RUNNING】: Thread 类里面没有这个状态 在就绪状态的线程获取到了 CPU 的使用权时候开始执行 run() 方法,该线程就处于运行状态
  • 阻塞状态【BLOCKED】:线程因为某种原因放弃了 CPU 的使用权,也让出了 cpu 时间片,暂时的停止运行直到线程再次获取到 CPU 的使用权,才有机会再次获得 CPU 的时间片转到运行状态。在同步阻塞的情况下,在获取到对象的同步锁时,如果该同步锁被其他的线程所占用,JVM 会把该线程放入到锁池中,当线程获取到锁的时,线程重新转为可运行状态
  • 等待状态【WAITING】:运行状态的线程执行 wait() 方法后, JVM 会把该线程放入等待队列
  • 限时等待【TIMED_WAITING】:运行状态的线程执行 sleep()/join() 方法后进入到限时等待状态,当 sleep() 超时 / join() 等待线程终止后,线程重新转为可运行状态
  • 终止【TERMINATED】:

在这里插入图片描述
在这里插入图片描述

线程的创建方式

  • 继承 Thread 类
  • 实现 Runnable 接口
  • 实现 Callable 接口,需要实现的是 call() 方法
  • 使用线程池

如何停止正在运行的线程

  • 正常地运行结束

  • 使用退出标志退出线程,使用 boolean 类型的标志,并通过这个标志的为 false/true 来控制 while 循环是否退出,同时需要使用 volatile 关键字进行修饰,这个关键的目的是使标志同步,并且修改标志位的值时让其他线程可见

  • interrupt() 结束线程

  • 如果一个线程由于等待某些事件的发生而阻塞【例:Thread.sleep()/Thread.join()/IO 等待时】,当调用 Thread.interrupt() 方法时,会抛出 InterruptException 异常,通过代码捕获该异常,然后 break 跳出循环。

    • 如果一个线程未处于阻塞状态:使用 isInterrupted() 方法判断线程的中断标志来退出循环,当使用 interrupt() 方法的时候中断标志会设置为 true,类似自定义标志退出循环

  • stop() 方法停止线程【不推荐】: Thread 类里面也提供了 stop() 方法来停止线程,但是该方法是很危险的,就像是突然关闭计算机,可能会产生不可预料的后果。在调用 Thread#stop() 方法之后,创建子线程的线程就会抛出 ThreadDeathError 的错误,并且会释放子线程的所有锁,一般情况下对加锁的代码都是为了保证数据的一致性,突然释放所有的锁可能被保护的数据出现不一致性。

wait() 方法与 sleep() 方法的区别

  • sleep() 方法是线程类 Thread 的静态方法,调用此方法设置休眠时间,会让出 CPU 的使用权给其他线程,但还是持有对象锁,当休眠时间结束时,该线程会回到就绪状态【RUNNABLE】
  • wait() 方法是 Object 类中的方法,调用 Object#wait() 方法后会导致当前线程放弃对象锁(线程暂停执行),也会让出 CPU 的使用权给其他线程,进入到等待池,只有针对此对象调用 Object#notify() 方法后该线程才准备获取对象锁进入运行状态【RUNNING】

为什么需要把 wait()/notify() 放在 synchronized 代码块里?

  • JAVA 的 API 强制要求放置在同步块中,如果不这样做会抛出 IllegalMonitorStateException 异常,还有一个原因是为了避免 Object#wait()、Object#notify() 之间产生竞态条件

start() 与 run() 方法的区别

  • start() 方法是真正启动一个线程,实现了多线程运行,调用了 start() 方法之后,此线程就进入到了就绪状态【RUNNABLE】
  • run() 方法里称之为线程体,包含了要执行这个线程的内容,线程就进入到了运行状态【RUNNING】,开始运行 run() 方法中的代码

ThreadLocal

ThreadLocal 又称之为 线程本地变量/线程本地存储, 是一个线程本地变量的工具类, 主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用, 减少同一线程内多个方法或者组件之间一些公共变量传递的复杂度
ThreadLocal 其实是一种以空间换时间的做法, 在每个 Thread 里面维护了一个 ThreadLocal.ThreadLocalMap, 把数据进行了隔离, 数据不进行共享, 自然就没有线程安全问题

  • 每个线程都有自己的一个 ThreadLocalMap 类对象, 可以将线程自己的对象保存在其中, 各个都是独立的, 线程可以正确访问到自己的对象
  • 将一个静态的 ThreadLocal 静态实例作为 key, 将不同对象的引用保存到不同线程的 ThreadLocalMap 中, 然后在线程执行的各处通过这个静态 ThreadLocal#get() 方法取得自己线程保存的那个对象, 避免了这个对象作为参数传递的麻烦
  • 一般情况下的使用场景是用来解决 数据库连接 或者 session管理 等

代码交流 2021