1.面试:
1.1 Sychrognized
1.Sychrognized是重入锁吗?为什么?
(1)可 重 入 性 是 锁 的 一 个 基 本 要 求 , 是 为 了 解 决 自 己 锁 死 自 己 的 情 况 。比 如
,一 个 类 中 的 同 步 方 法 调 用 另 一 个 同 步 方 法 , 假 如Synchronized 不 支 持 重 入
, 进 入 method2 方 法 时 当 前 线 程 获 得 锁 ,method2 方 法 里 面 执 行 method1 时 当 前
线 程 又 要 去 尝 试 获 取 锁 , 这时 如 果 不 支 持 重 入 , 它 就 要 等 释 放 , 把 自 己
阻 塞 , 导 致 自 己 锁 死 自 己 。
(2)对 Synchronized 来 说 , 可 重 入 性 是 显 而 易 见 的 , 在 执 行
monitorenter 指 令 时 , 如 果 这 个 对 象 没 有 锁 定 , 或 者 当 前 线 程 已 经 拥
有 了 这 个 对 象 的 锁 ( 而 不 是 已 拥 有 了 锁 则 不 能 继 续 获 取 ) , 就 把 锁 的 计
数 器 +1, 其 实 本 质 上 就 通 过 这 种 方 式 实 现 了 可 重 入 性 。
2.Sychrognized锁升级
jdk1.5之前为重量级锁(与操作系统内核交互)
jdk1.5之后:
markword记录这个线程的id(偏向锁)
如果存在锁竞争,升级为自旋锁(用户态进行自旋,效率较高)
自旋锁原理:自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需自旋,等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
线程自旋需消耗 cup 的,如果一直获取不到锁,则线程长时间占用CPU自旋,需要设定一个自旋等待最大事件在最大等待时间内仍未获得锁就会停止自旋进入阻塞状态。
如果自旋到达10次以后,升级为重量级锁(在内核态进行操作)
什么时候用自旋锁,什么时候用重量级锁?
当执行时间较长是用重量级锁,当执行时间较短时,使用自旋锁,但是当线程较多时,也不建议用自旋锁,消耗CPU资源。
1.2 CAS
CAS有3个操作数,内存值V,旧的预期值A(此期望值是从内存中读取出来的值),要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。如果V!=A,就是说期望的值A和V实际的值不相等了,那么证明肯定有其他线程改变了V的值,那么此时就会一直自旋,直到V==E,才会去更新B。
缺点:
1.ABA问题,解决方案:添加版本号 1A-->2B-->3A
2.循环时间长,开销大,如果有很多线程进行循环,会造成大量的CPU资源浪费。
3.只能保证一个共享变量的原子操作。如果是多个共享变量,那么CAS就无法保证多个共享变量的原子操作。Java jdk1.5引入了AutomicReference来保证引用对象之间的原子性,我们可以把多个变量放在一个对象中来进行CAS操作。
1.2ReetrantLock
可重入锁
1.3 ReetrantReadWriteLock
读写锁,读锁是共享锁,写锁是排他锁。因为在并发读取的过程中避免读到被修改的数据(脏读),所以需要读锁。在ReetrantReadWriteLock中读锁是共享锁,效率高(在同一时间内多个线程可以共同读),写锁是排他锁。
1.4 CountDownlatch 计数器
1.5 CyclicBarrier 栅栏
1.6 Semafore 信号量
可以设置允许N个线程同时执行,如果设置为1,那么能有一个线程1执行(前提是acquire方法拿到锁),其他线程必须等线程1的信号量释放后(release方法)才可以执行。
2.容器
为什么有了list和set,还需要Queue队列呢?list和set结构是装数据用的,队列是为了高并发准备的。阻塞队列,take和put操作,都是阻塞的,保证了并发安全。
2.1 ConcurrentHashMap
适用于高并发情况,并不是效率高,是为了保证线程安全
2.2 CopyOnWriteList
适用于读多写少的场景,原理是读数据的时候,会讲当前list复制一个新的list出来,读取的时候都读取这个新的list,类似于ReadAndWriteLock,读的时候使用共享锁,写的时候使用排他锁。
3.JMH
4.Disruptor
单机性能最好的消息队列MQ
5.interupt
当一个线程调用interupt函数中断后,并不会对当前线程立即中断,而是当获取锁之后,才会进行中断。