Java后端面试题(线程)(day10)

并发和并行的区别


  • 并发:两个及两个以上的作业看起来像是同时进行的,实际上它们是在交替执行

  • 并行:并行则强调真正的同时性,两个及两个以上的作业在物理上同时执行

线程和进程的区别?


  • 进程是程序运行和资源分配的基本单位,一个进程可以包含多个线程,而且最少拥有一个线程。

  • 线程是是cpu调度和分配的基本单位

线程有哪些状态?


NEW: 初始状态,线程被创建出来但没有被调用 start() 。
RUNNABLE: 运行状态,线程被调用了 start()等待运行的状态。
BLOCKED阻塞状态,需要等待锁释放。
WAITING等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
TIME_WAITING超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
TERMINATED终止状态,表示该线程已经运行完毕

创建线程的方式有哪些?有什么特点?


  1. 继承 Thread 类并重写 run 方法创建线程:实现简单但不可以继承其他类

  2. 实现 Runnable 接口并重写 run 方法:避免了单继承局限性,编程更加灵活,实现解耦。

  3. 实现 Callable 接口并重写 call 方法:可以获取线程执行结果的返回值,并且可以抛出异常

  4. 使用线程池创建(使用 java.util.concurrent.Executor 接口)

    扫描二维码关注公众号,回复: 17521042 查看本文章

Runnable 和 Callable 的区别?


  • Runnable 接口 run 方法无返回值,异常且无法捕获处理;

  • Callable 接口 call方法有返回值,支持泛型, 可以获取异常信息

如何启动一个新线程、调用 start 和 run 方法的区别?


  • run 方法只是 thread 的一个普通方法,线程对象调用 run 方法不开启线程
  • 调用 start 方法可以启动线程,使得线程进入就绪状态,并让 jvm 调用 run 方法在开启的线程中执行

Java中常见的锁


分类标准 分类
根据线程是否需要对资源加锁 悲观锁/乐观锁
根据多个线程是否能获取同一把锁 共享锁/独享(独占、排他)锁
根据锁是否能够重复获取 可重入锁/不可重入锁
根据锁的公平性进行区分 公平锁/非公平锁
当多个线程并发访问资源时,当使用synchronized时 锁升级( 偏向锁Q /轻量级锁/重量级锁)
根据资源被锁定后,线程是否阻塞 自旋锁/适应性自旋锁

公平锁与非公平锁

按照线程访问顺序获取对象锁。

synchronized是非公平锁,Lock默认是非公平锁,可以设置为公平锁,公平锁会影响性能

共享式与独占式锁

共享式与独占式的最主要区别在于:同一时刻独占式只能有一个线程获取同步状态,而共享式在同一时刻可以有多个线程获取同步状态。

例如读操作可以有多个线程同时进行,而写操作同一时刻只能有一个线程进行写操作,其他操作都会被阻塞。

悲观锁与乐观锁

  • 悲观锁,每次访问资源都会加锁,执行完同步代码释放锁,synchronizedReentrantLock属于悲观锁。

  • 乐观锁,不会锁定资源,所有的线程都能访问并修改同一个资源,如果没有冲突就修改成功并退出,否则就会继续循环尝试。

    乐观锁最常见的实现就是CAS

  • 适用场景:

    1. 悲观锁适合写操作多的场景。
    2. 乐观锁适合读操作多的场景,不加锁可以提升读操作的性能

Synchronized锁的升级过程


  • 一开始是无锁状态

  • 当一个线程首次获得对象锁时,JVM会设置为 偏向锁。

  • 当第二个线程尝试获取偏向锁失败时,偏向锁会升级为 轻量级锁

  • 此时,JVM会使用CAS自旋操作来尝试获取锁,如果成功则进入临界区域,否则升级为 重量级锁。

什么是CAS?


CAS全称Compare And Swap,比较与交换,乐观锁的主要实现方式

CAS在不使用锁的情况下实现多线程之间的变量同步。ReentrantLock内部的AQS和原子类内部都使用了CAS

线程相关的基本方法?


线程相关的基本方法有 waitnotifynotifyAllsleepjoinyield

  1. 线程等待(wait)
    调用wait方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中
    断才会返回,需要注意的是调用 wait()方法后,会释放对象的锁。

  2. 线程睡眠(sleep)
    sleep 导致当前线程休眠,进入 TIMED-WATING 状态,与 wait 方法不同的是 sleep 不会释放当前占有的锁,

  3. 线程让步(yield)
    yield 会使当前线程 让出 CPU 执行时间片,与其他线程一起重新竞争CPU 时间片。

  4. 线程中断(interrupt)
    中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。线程在合适的时候中断.

  5. Join 等待其他线程终止
    join() 方法,等待其他线程终止,当前线程再由阻塞状态变为就绪状态Runable.

  6. 线程唤醒

    • notify:唤醒在等待的 单个线程,被唤醒的线程会继续与其他线程进行竞争
    • notifyAll :唤醒等待的 所有线程

什么是线程死锁?死锁如何产生?


线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

  • 死锁产生条件

    1. 互斥:一个资源一次只能被一个线程持有。

    2. 请求与保持:一个进程因请求资源而阻塞时,不释放获得的资源。

    3. 不剥夺:进程已获得的资源,在未使用之前,不能强行剥夺。

    4. 循环等待:进程之间循环等待着资源

当以上条件同时满足时,就可能会出现死锁的情况。

如何避免线程死锁?


要避免线程死锁,可以采取以下几种方法:

  1. 尽量避免使用多个锁,尽量使用一个锁或者使用更加高级的锁,例如读写锁或者 ReentrantLock

  2. 减少锁的粒度, 确保同步代码块的执行时间尽可能短,这样可以减少线程等待时间,从而避免死锁的产生。

  3. 使用尝试锁,通过 ReentrantLock.tryLock() 方法可以尝试获取锁,如果在规定时间内获取不到锁,则放弃锁。

  4. 避免嵌套锁,如果需要使用多个锁,确保它们的获取顺序是一致的

wait和sleep有哪些区别?


相同点

  1. 它们都可以使当前线程暂停运行,把机会交给其他线程
  2. 任何线程在调用wait()和sleep()之后,在等待期间被中断都会抛出InterruptedException

不同点

  1. wait()是Object超类中的方法;而sleep()是线程Thread类中的方法
  2. 对锁的持有不同,wait()会释放锁,而sleep()并不释放锁
  3. 唤醒方法不完全相同,wait()依靠notify或者notifyAll 、中断、达到指定时间来唤醒;而sleep()到达指定时间被唤醒
  4. 调用wait()需要先获取对象的锁,而Thread.sleep()不用

JUC包提供了哪些原子类?


基本类型原子类

使用原子的方式更新基本类型

  • AtomicInteger:整型原子类
  • AtomicLong:长整型原子类
  • AtomicBoolean :布尔型原子类

数组类型原子类

使用原子的方式更新数组里的某个元素

  • AtomicIntegerArray:整形数组原子类
  • AtomicLongArray:长整形数组原子类
  • AtomicReferenceArray :引用类型数组原子类

引用类型原子类

  • AtomicReference:引用类型原子类
  • AtomicStampedReference:带有版本号的引用类型原子类。
  • AtomicMarkableReference :原子更新带有标记的引用类型。

JUC包常用的辅助类


Semaphore(信号量)

synchronizedReentrantLock 都是一次只允许一个线程访问某个资源,而Semaphore(信号量)可以用来控制同时访问特定资源的线程数量

Semaphore 有两种模式

  • 公平模式: 调用 acquire() 方法的顺序就是获取许可证的顺序,遵循 FIFO(先进先出)
  • 非公平模式默认,抢占式的

CountDownLatch (倒计时器)

CountDownLatch 用于某个线程等待其他线程执行完任务再执行,CountDownLatch 是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当 CountDownLatch 使用完毕后,它不能再次被使用

CyclicBarrier(循环栅栏)

CyclicBarrier 用于一组线程互相等待到某个状态,然后这组线程再同时执行,CyclicBarrier的计数器可以使用reset()方法重置,可用于处理更为复杂的业务场景

Lock和synchronized的区别


  • Lock 是接口,synchronized 是关键字
  • synchronized是非公平锁,Lock 接口支持公平锁和非公平锁
  • 当线程离开 synchronized 块或者方法时,锁会自动释放,使用 Lock 接口时,必须显式地调用lock()方法获取锁,并且在完成任务后显式地调用unlock()方法释放锁
  • synchronized锁不可被中断等待,除非锁被释放,Lock 接口可以通过调用lock.tryLock()方法尝试获取锁,如果失败则可以选择放弃等待

Lock 接口实现类有哪些?有什么作用?


  • ReentrantLock支持重入(即可以由持有锁的同一个线程多次获取)。可以指定公平策略。如果不指定,默认是非公平的。
  • ReentrantReadWriteLock:这个类实现了读写锁,允许多个读取者同时访问,但是一次只能有一个写入者。读取锁通常用于读多写少的情况,以提高并发性能。

synchronized的作用有哪些?


  1. 原子性:确保线程互斥的访问同步代码;

  2. 可见性:保证共享变量的修改能够及时可见;

  3. 有序性:有效解决重排序问题。

volatile关键字有什么用?


  1. volatile是轻量级的同步机制,volatile保证变量对所有线程的可见性不保证原子性
  2. 禁止进行指令重排序

什么是ThreadLocal?它的原理是什么?


ThreadLocal是一个线程本地变量,它可以为每一个线程都创建一个私有的变量,每个线程只能获取到自己的变量,从而避免了线程安全问题

ThreadLocal 的核心是 ThreadLocalMap 类,它是一个线程级别的哈希表,用于存储每个线程的变量。

ThreadLocalMapThreadLocal 对象作为 key,并且key是软引用
以变量值作为 value,value中的对象是强引用,每个线程都可以通过 ThreadLocalMap 获取自己的变量。

volatile和synchronized的区别是什么?


  1. volatile只能使用在变量上;而synchronized可以在类,变量,方法和代码块上。
  2. volatile至保证可见性synchronized保证原子性可见性
  3. volatile禁用指令重排序synchronized不会。
  4. volatile不会造成阻塞synchronized

ConcurrentHashMap原理


  • Java7 中 ConcurrentHashMap 使用的分段锁

    每一个 Segment 上同时只有一个线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变。
    结构

  • Java8 中的 ConcurrentHashMap 使用的 Synchronized 锁加 CAS 的机制

    结构由 Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表

猜你喜欢

转载自blog.csdn.net/qq_57036151/article/details/141280465