文章目录
- 一、什么是线程和进程?线程与进程的关系,区别及优缺点?
- 二、为什么要使用多线程呢?
- 三、说说线程的生命周期和状态?
- 四、什么是线程死锁?如何预防和避免线程死锁?
- 五、synchronized 关键字
- 六、并发编程的三个重要特性
- 七、JMM (Java Memory Model,Java 内存模型)和 happens-before 原则。
- 八、volatile 关键字
- 九、ThreadLocal 关键字
- 十、ReentrantLock 和 AQS
- 十一、乐观锁和悲观锁的区别
- 十一、CAS 了解么?原理?什么是 ABA 问题?ABA 问题怎么解决?
- 十二、Atomic 原子类
一、什么是线程和进程?线程与进程的关系,区别及优缺点?
提示:可以从从 JVM ⻆度说进程和线程之间的关系
首先说进程,进程其实就是程序的一次执行的过程。而线程与进程相似但是线程是比进程更小的单位。一个进程在其执行的过程中可以产生多个线程。线程是程序内部的一条执行路径,是程序中的一个单一的顺序控制流程。可以认为是进程里的一条执行路径。
区别:
从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的堆和方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。
总结: 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
二、为什么要使用多线程呢?
提示:从计算机⻆度来说主要是为了充分利⽤多核 CPU 的能⼒,从项⽬⻆度来说主要是为了提升系统的性能。
首先我们从计算机底层来说,线程可以比作是轻量级的进程,是程序执行的最小单位,线程之间的切换和调度的成本远远小于进程。另外,多核cpu时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。其次对于现在互联网发展的趋势来说, 现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
三、说说线程的生命周期和状态?
提示: 6 种状态( NEW 、 RUNNABLE 、 BLOCKED 、 WAITING 、 TIME_WAITING 、 TERMINATE D )。
线程的生命周期是由六种不同的状态的,首先线程被创建变为初始状态,线程调用start方法变成运行状态,在运行会发生阻塞或者等待,其中等待状态有两种分别是等待状态和超时等待状态,其中等待状态是表示该线程需要等待其他线程做出一些特定动作(通知或中断)。超时等待状态是在指定的时间后自行返回而不是像等待那样一直等待。阻塞状态是需要等待锁释放。最后是终止状态,表示该线程已经运行完毕了。
四、什么是线程死锁?如何预防和避免线程死锁?
这⾥最好能够结合代码来聊,你要确保⾃⼰可以写出有死锁问题的代码。
所谓线程死锁其实就是多个线程同时被阻塞,它们中的一个或者其他全部都在等待某个资源释放。由于线程无限期被阻塞,因此程序不能正常被终止。
其实预防死锁的方式只需要破坏产生死锁的必要条件即可,第一个破坏请求与保持条件(一次性申请所有的资源),第二个破坏不可剥夺条件(占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它所占有的资源),第三个破坏循环等待条件(靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。)
避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。
五、synchronized 关键字
提示: synchronized 关键字⼏乎是⾯试必问,你需要搞懂下⾯这些 synchronized 关键字相关的问题: 1.synchronized 关键字的作⽤,⾃⼰是怎么使⽤的。2.synchronized 关键字的底层原理(重点!!!)3.JDK1.6 之后的 synchronized 关键字底层做了哪些优化。 synchronized 锁升级流程。4.synchronized 和 ReentrantLock 的区别。5.synchronized 和 volatile 的区别。
首先,synchronized关键字翻译成中文是同步的意思,主要解决的是多个线程之间访问资源的同步性,被它修饰的方法或者代码块在任意时刻只能有一个线程执行。该关键字的使用方式主要用于三个地方分别是:1.修饰实例方法 2.修饰静态方法 3.修饰代码块。
底层原理 跟JVM中的对象监视器 monitor 有关。
synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。
.synchronized 和 ReentrantLock 的区别:ReentrantLock 实现了 Lock 接口,是一个可重入且独占式的锁,和 synchronized 关键字类似。不过,ReentrantLock 更灵活、更强大,增加了轮询、超时、中断、公平锁和非公平锁等高级功能。
synchronized 与volatile 区别在于,它们两个关键字是互补的存在,volatile 关键字是线程同步的轻量级实现,所以 volatile性能肯定比synchronized关键字要好 。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 。其次volatile 关键字可以保证数据的可见性,但不能办证数据的原子性。Synchronized都可以保证。volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。
六、并发编程的三个重要特性
提示: 原⼦性、可⻅性、有序性
分别是原子性、可见性、有序性。原子性是一次操作或者多次操作,要么所有的操作全部都得到执行并且不会受到任何因素的干扰而中断,要么都不执行。在 Java 中,可以借助synchronized、各种 Lock 以及各种原子类实现原子性。synchronized 和各种 Lock 可以保证任一时刻只有一个线程访问该代码块,因此可以保障原子性。
可见性是当一个线程对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新的值的。
有序性是保证指令不会受cpu指令并行优化(指令重排)的影响,由于指令重排序问题,代码的执行顺序未必就是编写代码时候的顺序。
七、JMM (Java Memory Model,Java 内存模型)和 happens-before 原则。
JMM模型是java内存模型,可以把 JMM 看作是 Java 定义的并发编程相关的一组规范,除了抽象了线程和主内存之间的关系之外,其还规定了从 Java 源代码到 CPU 可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范,其主要目的是为了简化多线程编程,增强程序可移植性的。
happens-before 原则是一种规则,用于区分事件发生的前后顺序。
八、volatile 关键字
提示: volatile 关键字同样是⼀个重点!结合 JMM (Java Memory Model,Java 内存模型)和 happens-before 原则来回答就⾏了。
volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
九、ThreadLocal 关键字
提示:关注 ThreadLocal 的底层原理、内存泄露问题以及⾃⼰是如何在项⽬中使⽤ ThreadLocal 关键字的。
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢?
JDK 中自带的ThreadLocal类正是为了解决这样的问题。 ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。ThreadLocal 就是用来避免这两个线程竞争的。
十、ReentrantLock 和 AQS
提示: ReentrantLock 的特性、实现原理(基于 AQS )。可以从 ReentrantLock 的实现来理解 AQS
ReentrantLock 和 AbstractQueuedSynchronizer(AQS) 是 Java 并发编程中的重要概念。
ReentrantLock 是一个可重入的互斥锁,由 Java 并发包中的 java.util.concurrent.locks.ReentrantLock 类实现。"可重入"意味着一个线程可以多次获取同一把锁,这在需要多次进入临界区的情况下非常有用。ReentrantLock 提供了与 synchronized 关键字类似的功能,但比 synchronized 更加灵活,因为它可以尝试获取锁(如果锁不可用,那么立即返回,而不是阻塞),并且可以中断等待锁的线程。
AbstractQueuedSynchronizer(AQS) 是 Java 并发包中的一个框架,用于构建依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量,事件,等待/通知等)。它使用一个整数状态表示获取或释放锁,子类需要定义改变这个状态的方法。ReentrantLock 就是基于 AQS 实现的。
AQS 通过内部的 FIFO 队列来管理阻塞的线程。如果请求的锁没有被其他线程持有,那么请求的线程可以成功获取锁;否则,这个线程就会被放入队列中,直到有其他线程释放锁为止。
总的来说,ReentrantLock 和 AQS 都是构建高效并发应用的重要工具,它们提供了一种更细粒度的锁控制方式,使得开发者能够更好地控制并发行为。
十一、乐观锁和悲观锁的区别
悲观锁::悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。
乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
十一、CAS 了解么?原理?什么是 ABA 问题?ABA 问题怎么解决?
提示:多地⽅都⽤到了 CAS ⽐如 ConcurrentHashMap 采⽤ CAS 和 synchronized 来保证并发安全,再⽐如 java.util.concurrent.atomic 包中的类通过 volatile+CAS 重试保证线程安全性。和⾯试官聊 CAS 的时候,你可以结合 CAS 的⼀些实际应⽤来说。
CAS,全称Compare And Swap,是一种无锁算法。在不使用传统锁的情况下,它通过比较内存中的值和预期值来决定是否更新内存中的值。基本操作是这样的:它包含三个参数CAS(V,E,N),V表示要更新的变量,E表示预期值,N表示新值。如果V值等于E值,则将V的值设为N。如果V值与E值不相等,则说明其他某个线程已经改变了V的值,那么本次操作就会失败。
ABA问题是CAS操作中可能会遇到的一个问题。假设V原来的值是A,它被线程1获取,就在这时,线程2也访问了V,将V的值由A改成B,然后又改回A,此时线程1进行CAS操作发现V的值仍然为A,然后线程1以为V的值没有被修改过,然后进行CAS操作,但实际上,V的值已经被线程2改变过了,这就是所谓的ABA问题。
对于ABA问题,一个常见的解决方案是使用版本号。在每次变量更新的时候,都把版本号加一,这样,即使A变成B,然后又变回A,版本号也会改变,这样就可以防止ABA问题。在Java中,AtomicStampedReference类就实现了这样的功能,它通过包装[E, S]对进行操作,其中E表示预期值,S表示版本号。当设置某个值时,它不仅检查值是否匹配,还会检查版本号是否也匹配,只有两者都匹配才会执行相应的操作。
十二、Atomic 原子类
原子类说简单点就是具有原子/原子操作特征的类。