一、synchronized关键字的底层原理
synchronized 同步语句块的实现,使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。
当执行 monitorenter 指令时,线程试图获取锁,也就是获取 monitor ( monitor 对象存在于每个 Java 对象的对象头中,synchronized 锁便是通过这种方式获取锁的,这也是为什么 Java 中任意对象都可以作为锁的原因) 的持有权。当计数器为0,则可以成功获取,获取后将锁计数器加1;相应的,在执行 monitorexit 指令后,将锁计数器减1,当值为0时,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
synchronized是支持可重入锁的,所以锁计数器可能加到2在执行两次monitorexit指令后,计数器的值为0,释放锁。
synchronized 修饰方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放
二、CAS的理解以及其底层实现原理
CAS是什么?
比较并交换,它是一条CPU并发原语。判断内存某个位置的值是否为预期值,如果是更改为新值,这个过程是原子的。
原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题
getAndIncrement()底层调用unsafe类方法,传入三个参数,unsafe.getAndAddInt() 底层使用CAS思想,如果比较成功加1,如果比较失败重新获得,再比较一次,直至成功。
Unsafe类:CAS的核心类,由于java方法无法直接访问底层系统,需要通过本地(native)方法来访问,基于该类可以直接操作特定内存的数据。
变量valueOffset:表示该变量在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。
value用volatile修饰:保证多线程之间的内存可见性
CAS缺点:
- 循环时间长开销大(如果CAS失败,会一直尝试)
- 只能保证一个共享变量的原子操作。(对多个共享变量操作时,循环CAS无法保证操作的原子性,只能用加锁来保证)
- 存在ABA问题
原子类AtomicInteger类ABA问题及解决方案
1、ABA问题是怎么产生的?
当第一个线程执行CAS(V,E,U)操作,在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值,这样我们就无法正确判断这个变量是否已被修改过。
2、ABA问题的解决方案:
AtomicStampedReference:是一个带有时间戳的对象引用,在每次修改后,不仅会设置新值还会记录更改的时间。
AtomicMarkableReference:维护的是一个boolean值的标识,这种方式并不能完全防止ABA问题的发生,只能减少ABA发生的概率。
三、ConcurrentHashMap实现线程安全的底层原理
ConcurrentHashMap实现线程安全的底层原理到底是什么?
1,JDK 1.8以前,多个数组,分段加锁,一个数组一个锁。
2,JDK 1.8以后,优化细粒度,一个数组,每个元素先进行CAS,如果失败说明有人put过值了,此时synchronized对这个数组元素加锁,链表+红黑树处理。jdk1.8+是对数组每个元素加锁。
四、你对JDK中的AQS理解吗?AQS的实现原理是什么?
AQS :Abstract Queue Synchronizer,抽象队列同步器。
AQS的功能分为两种:独占和共享
- 独占锁,每次只能有一个线程持有锁,例如ReentrantLock就是以独占方式实现的互斥锁
- 共享锁,允许多个线程同时获取锁,并发访问共享资源,例如ReentrantReadWriteLock
AQS原理:AQS的实现依赖内部的同步队列,也就是FIFO的双向队列,如果当前线程竞争锁失败,那么AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程。当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。
当添加节点时:节点添加至队列尾部,prev指向之前末尾节点,之前末尾节点的next指向当前节点,tail指向该节点
移出节点时:首部节点的下一个节点将prev的指针指向null,head指向首部节点的下一个节点
AQS实现原理:
当lock.lock()的时候,实际上底层是由AQS来完成的加锁,AQS提供了一个state表示加锁状态,默认0表示不加锁,Thread的属性存放加锁线程;
当加锁的时候,通过CAS操作将state+1,则成功获取锁,CAS操作达到了加锁的互斥效果
锁的可重入性,就是通过state不断+1实现,对一个ReentrantLock不断加锁,则state不断+1,释放锁则-1
当加锁失败后,会把失败线程加到AQS中的队列里,等待获取锁
如果是公平锁,当释放锁后会从队列头结点来获取线程加锁,非公平锁则可能新来的线程也可能抢到锁
五、线程池的底层工作原理
// int corePoolSize:线程池维护线程的最小数量.
// int maximumPoolSize:线程池维护线程的最大数量.
// long keepAliveTime:空闲线程的存活时间.
// TimeUnit unit: 时间单位,现有纳秒,微秒,毫秒,秒枚举值.
// BlockingQueue<Runnable> workQueue:持有等待执行的任务队列.
// RejectedExecutionHandler handler:
// 用来拒绝一个任务的执行,有两种情况会发生这种情况。
// 一是在execute方法中若addIfUnderMaximumPoolSize(command)为false,即线程池已经饱和;
// 二是在execute方法中, 发现runState!=RUNNING || poolSize == 0,即已经shutdown,就调用ensureQueuedTaskHandled(Runnable command),在该方法中有可能调用reject。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutor池子的处理流程如下:
1)当池子大小小于corePoolSize就新建线程,并处理请求
2)当池子大小等于corePoolSize,把请求放入workQueue中,池子里的空闲线程就去从workQueue中取任务并处理
3)当workQueue放不下新入的任务时,新建线程入池,并处理请求,如果池子大小撑到了maximumPoolSize就用RejectedExecutionHandler来做拒绝处理
4)另外,当池子的线程数大于corePoolSize的时候,多余的线程会等待keepAliveTime长的时间,如果无请求可处理就自行销毁
其会优先创建 CorePoolSiz 线程, 当继续增加线程时,先放入Queue中,当 CorePoolSiz 和 Queue 都满的时候,就增加创建新线程,当线程达到MaxPoolSize的时候,就会抛出错 误 org.springframework.core.task.TaskRejectedException
另外MaxPoolSize的设定如果比系统支持的线程数还要大时,会抛出java.lang.OutOfMemoryError: unable to create new native thread 异常。
Reject策略预定义有四种:
(1)ThreadPoolExecutor.AbortPolicy:是默认的策略,处理程序遭到拒绝将抛出运行时 RejectedExecutionException。
(2)ThreadPoolExecutor.CallerRunsPolicy:,调用者的线程会执行该任务,如果执行器已关闭,则丢弃.
(3)ThreadPoolExecutor.DiscardPolicy:不能执行的任务将被丢弃.
(4)ThreadPoolExecutor.DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程).