volatile关键词的作用是什么

适用于多线程环境,限制代码重排序优化,提供可见性保证。不保证原子性,不能替代锁。

volatile如何保证线间可见和避免指今重排

  1. 可见性:对于被声明为 volatile 的变量,在一个线程中的写操作会立即刷新到主内存,同时,其他线程读取该变量时会从主内存中获取最新的值。这样可以确保对该变量的修改对其他线程是可见的,避免了线程之间的数据不一致性。
  2. 禁止指令重排序:volatile 关键字禁止编译器和处理器对被修饰变量相关指令进行重排序优化。这样可以确保在多线程环境下,程序的执行顺序与程序员编写的顺序保持一致,避免了可能导致并发问题的指令重排序。

创建线程的几种方式

  1. 继承Thread类并重写run方法,然后通过创建该类的实例来启动线程。
  2. 实现Runnable接口并实现run方法,然后通过创建Thread对象,将Runnable实例作为参数传递给Thread,并调用start()方法启动线程。
  3. 使用线程池来管理和复用线程,可以通过Executors类的工厂方法创建不同类型的线程池。通过将Runnable或Callable实例提交给线程池,线程池会自动调度和执行任务。

调用start方法和调用run方法的区别

start是启动线程的方法,run是线程中方法的具体实现,如果直接调用run方法他不会创建一个新的线程

线程的常用调度方法

  1. sleep 让当前线程休眠指定的毫秒数。
  2. yield 用于提高线程的执行效率,但不能保证一定会让出CPU。
  3. join 会阻塞当前线程,直到被调用的线程执行完毕后才会继续执行当前线程。
  4. wait notify notifyAll 这些方法用于实现线程间的协作和通信。
  5. interrupt 需要在线程的代码中通过检查中断标志位,并做相应处理来实现线程的中断。

线程的状态有几种

  1. 新建 New
  2. 运行 Runnable
  3. 阻塞 Blocked
  4. 等待 Waiting
  5. 超时等待 Timed Waiting
  6. 终止 Terminated

ThreadLocal本质是什么

是一种线程封闭的机制,它是Java中的一个类,用于在多线程环境下为每个线程提供独立的变量副本,使得每个线程都可以访问自己独立的变量,互不干扰。使用他可能会造成内存泄露,所以要注意管理好变量的生命周期,确保在适当的时候进行清理和回收,以免造成资源浪费。

ThreadLocal内存泄漏是什么引起的

ThreadLocal内存泄漏通常由于线程池未正确清理或ThreadLocal对象生命周期过长。在使用ThreadLocal时,要及时清理ThreadLocal,特别是在使用线程池时,确保在线程执行前或执行后重置ThreadLocal,以避免内存泄漏。

ThreadLocalMap的结构

他是ThreadLocal类中的一个内部类,用于在多线程环境下为每个线程提供独立的变量副本。它是一个哈希表,使用ThreadLocal对象的弱引用作为key,存储线程独立的变量副本作为value。通过ThreadLocalMap,每个线程都可以独立地维护自己的变量副本,实现了线程级别的数据隔离。ThreadLocalMap使用开放地址法解决哈希冲突,并在扩容时进行再散列。为了避免内存泄漏,使用完ThreadLocal后,应该及时调用remove()方法清理当前线程的ThreadLocal副本。

父子线程怎么共享数据

  1. 通过在父线程和子线程之间使用共享变量来实现数据共享。
  2. 使用ThreadLocal可以在父线程和子线程之间实现数据隔离,每个线程都有独立的变量副本。父线程设置ThreadLocal变量,子线程可以通过ThreadLocal访问和修改自己的副本。
  3. 父线程和子线程可以通过线程通信机制,如wait/notify或者Lock/Condition来实现数据的交换和共享。
  4. 使用线程池中的ThreadLocal:如果使用线程池创建线程,可以通过ThreadLocal在父线程和子线程之间传递数据。

synchronized锁升级过程

在没有竞争的情况线程可以直接访问不需要加锁,当有一个线程访问被synchronized修饰的代码块的时候jvm会给他上一个偏向锁,这个线程可以在未来的执行中直接获取锁,无需再进行同步操作,提高了单线程场景下的性能,当多线程尝试获取同一个锁时JVM会尝试将偏向锁升级为轻量锁,适用于锁竞争不激烈的情况,如果轻量锁尝试失败或者出现堵塞就会升级重度锁。

synchronized锁优化

synchronized锁的优化包括偏向锁、轻量级锁、自旋锁、锁消除和锁粗化。偏向锁适用于无竞争情况,轻量级锁适用于竞争不激烈的情况,自旋锁避免线程阻塞,锁消除消除不必要的锁开销,锁粗化合并连续的同步操作。

synchronized锁消除

这个是在jvm在运行的时候会进行逃逸分析的优化措施,当jvm发现这个代码不会被其他线程所访问的时候就会去自动消除synchronized锁,他在默认情况是开启的,可以配置JVM参数来关闭他。

synchronized和ReetrantLock的区别

synchronized是java中的关键字而ReetrantLock是JDK的API

在1.6以后性能就都差不多了因为synchronized加入了适应性自旋、锁消除等

CAS 呢?CAS 了解多少?

他是一种并发控制机制,用于实现多线程环境下的原子操作。

CAS操作涉及三个操作数:内存位置(或称为变量)、期望的旧值和新值。CAS操作会检查内存位置的值是否等于期望的旧值,如果相等,则将内存位置的值更新为新值;如果不相等,则说明其他线程已经修改了内存位置的值,CAS操作失败。

CAS 有什么问题?如何解决?

ABA 问题

当多个线程在同一时间修改一个东西,比如一个数值,有时候会出现一个问题。想象你有一个数值是A,然后有一个人把它改成了B,又改回了A。如果另一个人要检查这个数值是不是A,他可能会发现是A,因为最后的状态是A。但实际上在中间有过变化。

每次修改变量,都在这个变量的版本号上加 1,这样,刚刚 A->B->A,虽然 A 的值没变,但是它的版本号已经变了,再判断版本号就会发现此时的 A 已经被改过了。

循环性能开销

自旋 CAS,如果一直循环执行,一直不成功,会给 CPU 带来非常大的执行开销。

可以限制自旋次数,超过就停止自旋

只能保证一个变量的原子操作

CAS 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性的。

  • 可以考虑改用锁来保证操作的原子性
  • 可以考虑合并多个变量,将多个变量封装成一个对象,通过 AtomicReference 来保证原子性。

原子操作类了解多少?

Atomic 包里的类基本都是使用 Unsafe 实现的包装类。

使用原子的方式更新基本类型,Atomic 包提供了以下 3 个类:

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

通过原子的方式更新数组里的某个元素,Atomic 包提供了以下 4 个类:

  • AtomicIntegerArray:原子更新整型数组里的元素。
  • AtomicLongArray:原子更新长整型数组里的元素。
  • AtomicReferenceArray:原子更新引用类型数组里的元素。
  • AtomicIntegerArray 类主要是提供原子的方式更新数组里的整型

原子更新基本类型的 AtomicInteger,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic 包提供了以下 3 个类:

  • AtomicReference:原子更新引用类型。
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
  • AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是 AtomicMarkableReference(V initialRef,boolean initialMark)。

如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic 包提供了以下 3 个类进行原子字段更新:

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。

线程死锁了解吗?该如何避免?

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去。

  1. 如果多个线程需要获取多个资源,可以按照相同的顺序获取,避免循环等待。
  2. 可以在尝试获取资源时设置一个超时时间,如果在规定时间内没有获取到资源,就放弃或者尝试其他途径。
  3. 将资源分为不同的级别,要求线程按照顺序获取资源,避免出现循环等待的情况。
  4. 通过wait()notify()等机制,让线程在获取不到资源时进入等待状态,等到资源释放时再被唤醒。

什么是线程池?

他可以帮我们管理线程避免线程的重复创建和销毁,可以重复利用线程

ThreadPoolExecutor 的参数有哪些

  1. 核心线程数
  2. 最大线程数量
  3. 线程存活时间
  4. 存活时间单位
  5. 工作队列
  6. 线程工厂
  7. 拒绝策略

给线程池提交一个任务的流程是什么样子的

当任务提交给线程池后会先判断是否小于核心线程数,如果小于就会直接创建线程,如果大于等于就会放到工作队列中,放到队列前会判断一下是否已满,如果没满就放到队列中,如果满了就会执行拒绝策略。

线程池的拒绝策略有哪些?

  • AbortPolicy :直接抛出异常,默认使用此策略
  • CallerRunsPolicy:用调用者所在的线程来执行任务
  • DiscardOldestPolicy:丢弃阻塞队列里最老的任务,也就是队列里靠前的任务
  • DiscardPolicy :当前任务直接丢弃

线程池怎么关闭知道吗?

shutdown() 方法和 shutdownNow()

shutdown()方法是将线程池调整为关闭状态,要等所有线程执行完毕后再关闭

shutdownNow()方法能立即停止线程池,正在跑的和正在等待的任务都停下了。

线程池异常怎么处理知道吗?

  • 使用try-catch块捕获异常,并将异常信息记录到日志中
  • 在捕获到异常后,如果情况允许,可以将异常重新抛出,以便上层代码或其他处理机制能够更好地处理异常。
  • 确保线程池中的线程能够正确释放占用的资源,避免资源泄露。
  • 在异常处理时,需要考虑任务的状态,避免重复处理或者对已经完成的任务进行异常处理。
  • 设置监控机制,及时检测线程池异常情况,可以使用监控工具或者自定义报警机制,让开发人员及时介入。
最后修改:2023 年 08 月 27 日
如果觉得我的文章对你有用,请随意赞赏