volatile关键词的作用是什么
适用于多线程环境,限制代码重排序优化,提供可见性保证。不保证原子性,不能替代锁。
volatile如何保证线间可见和避免指今重排
- 可见性:对于被声明为
volatile
的变量,在一个线程中的写操作会立即刷新到主内存,同时,其他线程读取该变量时会从主内存中获取最新的值。这样可以确保对该变量的修改对其他线程是可见的,避免了线程之间的数据不一致性。 - 禁止指令重排序:
volatile
关键字禁止编译器和处理器对被修饰变量相关指令进行重排序优化。这样可以确保在多线程环境下,程序的执行顺序与程序员编写的顺序保持一致,避免了可能导致并发问题的指令重排序。
创建线程的几种方式
- 继承Thread类并重写run方法,然后通过创建该类的实例来启动线程。
- 实现Runnable接口并实现run方法,然后通过创建Thread对象,将Runnable实例作为参数传递给Thread,并调用start()方法启动线程。
- 使用线程池来管理和复用线程,可以通过Executors类的工厂方法创建不同类型的线程池。通过将Runnable或Callable实例提交给线程池,线程池会自动调度和执行任务。
调用start方法和调用run方法的区别
start是启动线程的方法,run是线程中方法的具体实现,如果直接调用run方法他不会创建一个新的线程
线程的常用调度方法
sleep
让当前线程休眠指定的毫秒数。yield
用于提高线程的执行效率,但不能保证一定会让出CPU。join
会阻塞当前线程,直到被调用的线程执行完毕后才会继续执行当前线程。wait
notify
notifyAll
这些方法用于实现线程间的协作和通信。interrupt
需要在线程的代码中通过检查中断标志位,并做相应处理来实现线程的中断。
线程的状态有几种
- 新建 New
- 运行 Runnable
- 阻塞 Blocked
- 等待 Waiting
- 超时等待 Timed Waiting
- 终止 Terminated
ThreadLocal本质是什么
是一种线程封闭的机制,它是Java中的一个类,用于在多线程环境下为每个线程提供独立的变量副本,使得每个线程都可以访问自己独立的变量,互不干扰。使用他可能会造成内存泄露,所以要注意管理好变量的生命周期,确保在适当的时候进行清理和回收,以免造成资源浪费。
ThreadLocal内存泄漏是什么引起的
ThreadLocal内存泄漏通常由于线程池未正确清理或ThreadLocal对象生命周期过长。在使用ThreadLocal时,要及时清理ThreadLocal,特别是在使用线程池时,确保在线程执行前或执行后重置ThreadLocal,以避免内存泄漏。
ThreadLocalMap的结构
他是ThreadLocal类中的一个内部类,用于在多线程环境下为每个线程提供独立的变量副本。它是一个哈希表,使用ThreadLocal对象的弱引用作为key,存储线程独立的变量副本作为value。通过ThreadLocalMap,每个线程都可以独立地维护自己的变量副本,实现了线程级别的数据隔离。ThreadLocalMap使用开放地址法解决哈希冲突,并在扩容时进行再散列。为了避免内存泄漏,使用完ThreadLocal后,应该及时调用remove()
方法清理当前线程的ThreadLocal副本。
父子线程怎么共享数据
- 通过在父线程和子线程之间使用共享变量来实现数据共享。
- 使用ThreadLocal可以在父线程和子线程之间实现数据隔离,每个线程都有独立的变量副本。父线程设置ThreadLocal变量,子线程可以通过ThreadLocal访问和修改自己的副本。
- 父线程和子线程可以通过线程通信机制,如wait/notify或者Lock/Condition来实现数据的交换和共享。
- 使用线程池中的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 问题。
线程死锁了解吗?该如何避免?
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去。
- 如果多个线程需要获取多个资源,可以按照相同的顺序获取,避免循环等待。
- 可以在尝试获取资源时设置一个超时时间,如果在规定时间内没有获取到资源,就放弃或者尝试其他途径。
- 将资源分为不同的级别,要求线程按照顺序获取资源,避免出现循环等待的情况。
- 通过
wait()
和notify()
等机制,让线程在获取不到资源时进入等待状态,等到资源释放时再被唤醒。
什么是线程池?
他可以帮我们管理线程避免线程的重复创建和销毁,可以重复利用线程
ThreadPoolExecutor 的参数有哪些
- 核心线程数
- 最大线程数量
- 线程存活时间
- 存活时间单位
- 工作队列
- 线程工厂
- 拒绝策略
给线程池提交一个任务的流程是什么样子的
当任务提交给线程池后会先判断是否小于核心线程数,如果小于就会直接创建线程,如果大于等于就会放到工作队列中,放到队列前会判断一下是否已满,如果没满就放到队列中,如果满了就会执行拒绝策略。
线程池的拒绝策略有哪些?
- AbortPolicy :直接抛出异常,默认使用此策略
- CallerRunsPolicy:用调用者所在的线程来执行任务
- DiscardOldestPolicy:丢弃阻塞队列里最老的任务,也就是队列里靠前的任务
- DiscardPolicy :当前任务直接丢弃
线程池怎么关闭知道吗?
有shutdown() 方法和 shutdownNow()
shutdown()方法是将线程池调整为关闭状态,要等所有线程执行完毕后再关闭
shutdownNow()方法能立即停止线程池,正在跑的和正在等待的任务都停下了。
线程池异常怎么处理知道吗?
- 使用try-catch块捕获异常,并将异常信息记录到日志中
- 在捕获到异常后,如果情况允许,可以将异常重新抛出,以便上层代码或其他处理机制能够更好地处理异常。
- 确保线程池中的线程能够正确释放占用的资源,避免资源泄露。
- 在异常处理时,需要考虑任务的状态,避免重复处理或者对已经完成的任务进行异常处理。
- 设置监控机制,及时检测线程池异常情况,可以使用监控工具或者自定义报警机制,让开发人员及时介入。