JUC
关键字
synchronized的锁优化有哪些、讲一下锁状态和锁升级
优化 Monitor 这类的重量级锁 (轻量级锁)
每个线程中的栈帧都会包含一个锁记录对象(Lock Record),
内部可以通过 CAS 的方式存储锁定对象的 Mark Word(从而不再一开始就使用 Monitor)自旋优化
当升级到重量级锁竞争时,如果发生竞争失败不会立即进入到 EntryList 进行阻塞,
而是会重试一会儿再阻塞优化轻量级锁重入(偏向锁)
轻量级锁在没有竞争时,每次重入操作仍需要 CAS,为了避免性能降低,
所以引入了偏向锁优化轻量级锁重入,在第一次 CAS 时会将线程的 ID 写入对象的 Mark Word 中
此后线程发现锁定对象中的 Mark Word 存在自己的线程 ID,则不会再次进行 CAS,
因为这个对象就归这个线程所有
voliate关键字
原理:
内存屏障,Memory Barrier(Memory Fence)
对 volatile 变量的写指令后会加入写屏障(屏障之前,对贡献变量的修改都是会同步到主存中)
对 volatile 变量的读指令前会加入读屏障(屏障之后,对共享变量的读取都是主存中的新数据)
作用:
一、确保可见性
二、确保有序性
保持内存可见性。所有线程都能看到共享内存的最新状态。每次读取前必须先从主内存刷新最新的值。每次写入后必须立即同步回主内存当中。
禁止指令重排。提供内存屏障的方式来防止指令被重排,编译器在生成字节码文件时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
主要类
什么情况下用 ReentrantLock 而不用 synchronized
- 阻塞时可被中断
- 可以设置获取锁的超时时间
- 可以设置为公平锁
- 支持多个条件变量(condition)
Java的中断怎么实现,为什么 synchronized 不能中断,ReentrantLock 可以中断
- synchronized 是在JVM层面上实现的,在字节码中会有 monitorenter、monitorexit 介入,
会自动释放锁定(代码执行完成或者出现异常) - ReentrantLock 是实现 lock 接口和内部类继承 AQS 实现的,是通过代码实现的
ReentrantLock 怎么实现的(AQS)
通过实现 lock 接口以及结合继承了 AQS 的内部类 Sync 实现的
AQS源码看过吗?能说一下么?
- 使用 CLH 队列,实现线程阻塞等待以及被唤醒时锁分配的机制
- 独享模式:
1 | |
- 共享模式:
1 | |
- 条件队列
1 | |
ThreadLocal,底层如何实现
每个线程内都有一个ThreadLocalMap类型的成员变量,用来存储资源对象
ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,
那么但凡有一个线程对这个变量进行 set 操作时,这个线程中的 threadLocals 属性就会被创建赋值
所以当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题
1 | |
工具类
Java并发包下的原子工具类,能说一下么?源码看过吗?
AtomicBoolean
AtomicInteger
AtomicLong
AtomicReference
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
都是以CAS方式确保原子性,但是可能会触发ABA问题
概念
为什么使用CAS就能保证并发?
无需阻塞等待,立马执行,立马返回成功或失败
ABA问题及解决办法
- AtomicStampedReference 需要我们传入整型变量作为版本号,来判定是否被更改过
但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,就使用:
2. AtomicMarkableReference 需要我们传入布尔变量作为标记,来判断是否被更改过
IO密集和CPU密集两种情况下,线程池里的线程数应该怎么设置
IO密集型的话,是指系统大部分时间在跟I/O交互,而这个时间线程不会占用CPU来处理,
即在这个时间范围内,可以由其他线程来使用CPU,因而可以多配置一些线程。 2 * n
CPU密集型的话,一般配置CPU处理器个数+1个线程, n + 1
所谓CPU密集型就是指系统大部分时间是在做程序正常的计算任务,
例如数字运算、赋值、分配内存、内存拷贝、循环、查找、排序等,这些处理都需要CPU来完成。
Java中创建多线程的方式有哪些
一、继承Thread,重写run方法;方便传参,但不支持多继承
二、使用Runnable配合Thread;解耦强,灵活
三、FutureTask结合Thread;可以获得放回结果
乐观锁和悲观锁的区别
悲观锁:拿到资源时,就对资源上锁,并在提交后,才释放锁资源,其他线程才能使用资源。
乐观锁:拿到资源时,上乐观锁,在提交之前,其他的锁也可以操作这个资源,当有冲突的时候,并发机制会保留前一个提交,打回后一个提交,让后一个线程重新获取资源后,再操作,然后提交。类似于 Git
吞吐量:乐观锁 > 悲观锁
适用场景:读取频繁用乐观锁,写入频繁用悲观锁
特别地,如果吞吐量大,但是乐观锁获取锁的所消耗的性能又高,这个时候就不推荐适用乐观锁了
什么是死锁,怎么破坏死锁
死锁成立的四个条件:
互斥:某种资源只允许一个进程访问
占有且等待:一个进程本身占有了资源(一个或多个),同时还有资源未得到满足,正在等待其他进程释放该资源
不可抢占:无法抢占别人占有的资源
循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源
打破上述任何一个条件,便可让死锁消失
线程池
线程池有哪些参数
1.核心线程数
2.最大线程数
3.保持存活时间
4.时间单位
5.线程工厂
6.阻塞队列
7.拒绝策略
执行流程是什么?
当一个任务传给线程池以后,可能有以下几种可能
1.将任务分配给一个核心线程来执行
2.核心线程都在执行任务,将任务放到阻塞队列workQueue中等待被执行
3.阻塞队列满了,使用救急线程来执行任务;救急线程用完以后,超过生存时间(keepAliveTime)后会被释放
4.任务总数大于了 最大线程数(maximumPoolSize)与阻塞队列容量的最大值(workQueue.capacity),使用拒接策略
拒绝策略有哪些?
一、AbortPolicy(堕胎政策-默认):直接抛出RejectedExecutionException异常阻止系统正常运行
二、CallerRunsPolicy (调用者运行政策):一种调节机制,该策略既不会抛弃任务,也不会抛出异常,
而是将某些任务回退到调用者,从而降低新任务的流量
三、DiscardOldestPolicy(丢弃老的政策):抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
四、DiscardPolicy(丢弃政策):该策略默默地丢弃无法处理的任务,无予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!