文章目录
回顾
ThreadLocal缺点
- 不可继承性(子线程不能读取父线程的变量)
- 脏数据(ThreadPool[复用]线程的复用会复用线程相关的静态变量)
- 内存溢出[原因:ThreadPool(长生命周期 )–>Thread -->ThredLocal—>ThreadLocalMap --> Entry --> key,value(强引用)]
单例模式
- 饿汉方式(线程安全的)
- 懒汉方式(线程安全)DCL 双重校验锁
阻塞式队列
生产者在队列满的情况下就会休眠(sleep、wait、LockSupport)
消费者在队列为空的时候进行休眠
锁
乐观锁
它认为一般情况下,不会发生冲突,所以只有在进行数据更新的时候,才会检测并发冲突,如果没有冲突则直接修改,如果有冲突则返回失败
CAS、ABA、JUC
CAS(Compare and swap:比较且交换)
V(内存值)
A(预期旧值)
B(新值)
实现
Atomic*
解决线程不安全问题
CAS底层实现原理
java层面CAS的实现是Unsafe类
Unsafe类调用了C++的本地方法,通过调用操作系统的Atomic::cmpxchg(原子指令)来实现CAS操作
乐观锁性能比较高
CAS的缺点
带来ABA问题
AtomicInteger 是存在ABA问题
A:预期旧值,B表示新值
eg:
ABA问题解决方案
引入版本号,每次操作完(修改)+1(更新版本号)
AtomicStampedReference(无ABA问题)
AtomicReference(有ABA问题)
eg:
Integer 高速缓存(-128~127)
设置应用程序的参数:-D
设置Integer高速缓存的最大值:
悲观锁
它认为通常情况下会出现并发冲突,所以在一开始就会加锁
synchronized 是悲观锁
共享锁
一把锁可以被多个程序拥有
读写锁中的读锁就是共享锁
独占锁(非共享锁)
一把锁只能被一个线程拥有
synchronized 是非共享锁
读写锁中的写锁就是非共享锁
读写锁
ReentrantReadWriteLock
将一把锁分成两个,读锁(读数据)、写锁
读锁是可以被多个线程同时拥有的,而写锁只能被一个线程拥有
优点
锁的力度更小,性能更高
注意
读写锁中的读锁和写锁是互斥的(防止同时读写所产生的脏数据)
公平锁
eg:new ReentrantLock(true)
锁的获取顺序必须和线程访问的先后顺序保持一致
优点
执行有序,执行结果可预期
非公平锁
eg:new ReentrantLock(false)/new ReentrantLock()/synchronized
锁的获取顺序和线程访问的先后顺序无关(默认锁策略)
优点
性能更高
自旋锁
synchronized轻量级锁时
通过死循环一直尝试获取锁
while{
if(尝试获取锁){
return;
}
}
缺点
如果发生死锁,则会一直自旋(循环),所以会带来一定的额外开销,消耗系统性能
可重入锁
当一个线程获取一个锁之后,可以重复的进入
synchronized (lock){
System.out.println("第一次进入锁");
synchronized (lock){
System.out.println("第二次进入锁");
}
}
问题
乐观锁和悲观锁具体怎么实现呢?
- 乐观锁:CAS—>Atomic*
CAS 组成是由V(内存值)、A(预期旧值)、B(新值),执行时,使用V==A对比,若结果为true则表明没有并发冲突,可以直接修改,否则不能修改
CAS是通过调用C++实现提供的Unsafe中的本地方法CompareAndSwapXXX来实现的,C++是通过操作系统的原子指令(Atomic::cmpxchg)来实现的- 悲观锁:
synchronized- 在Java层面是将锁的ID存放到对象头来实现的
偏向锁的实现:
在对象头中有一个偏向锁ID,可以把自己的线程ID放到偏向锁ID中,每次当有线程访问时,就拿到当前访问它的线程ID和对象头中的偏向锁ID进行对比,如果相等,则表示这个线程拥有了这把锁,直接执行相应代码,否则表示这把锁不属于这个线程,这时,通过自旋,来尝试获取这把锁- synchronized在JVM层面是通过监视器锁来实现的
- synchronized在操作系统层面是通过互斥锁来实现的
java的synchronized是怎么实现的?
- synchronized来修饰代码块、静态方法、普通方法来实现加锁
- 在写完synchronized标识后,JVM编译器在生成代码时,会加入一个监视器锁的进入和退出,监视器锁是通过调用操作系统的互斥锁来实现的
synchronized的锁优化?
- 锁优化(锁消除)
- JDK1.6锁升级的过程
无锁—>(第一个线程第一次访问时升级为)偏向锁(将线程ID存储在对象头中的偏向锁标识中)—>轻量级锁(自旋)—>重量级锁(实现用户内存到系统内存的切换(性能低))