多线程与高并发
锁
CAS
compare and swap
比较并替换
过程
原值 == 》 O
读取值 == 》 E = O
计算值 == 》 A = 计算E
比较值 E == O ? O = A : E = O重复过程
ABA问题
当线程A读取到值后,原值被其他线程修改,最终又回到原值,但已经不是之前的值,这时线程A进行比较原值,还会进行之前的操作,但操作的已经不是之前的原值
# 解决方法
使用版本号
每次进行操作,都对版本号进行修改
比较时,带上版本号一起比较
JDK中的类
AtomicStampedReference
/**
* AtomicStampedReference类型中使用了版本戳,解决了ABA问题
* 但是可能在比较之后写入的过程中会导致ABA问题
* 追溯底层源码,compareAndSwap方法底层使用c++语言调用汇编语言方法
* lock cmpxchg 执行方法时,锁定cpu不允许其他cpu操作 compareAndExchange
*/
String a = "aaa";
AtomicStampedReference i = new AtomicStampedReference(a, 1);
i.compareAndSet(i.getReference(), i.getReference() + "b", i.getStamp(), i.getStamp() + 1);
System.out.println(i.getReference());
对象在内存中的内存布局
JOL Java Object Layout
<dependencies>
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
</dependencies>
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
64位系统默认都是8个字节
markword 8个字节
class pointer 默认是压缩指针 java开启时默认参数 UsePrecessedClassPointer 4个字节
markword + class pointer = 对象头
instence data 实例数据 无数据默认 0
padding 补齐 字节为8的倍数效率最好,所以padding会使用空字节补齐
所以在默认压缩的情况下 8 + 4 + 4(空) = 16个字节
所以 Object o = new Object() 16个字节 如果加上引用对象 o 就是20个字节 引用对象指针默认也开启压缩指针
Class User{
private int id;
Private String name;
}
markword 8
class pointer 4
int 4
String 引用对象 4
8 + 4 + 4 + 4 = 20 + 4(空) = 24
给对象加锁时,锁的信息保存在markword中
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
锁升级过程
new - 偏向锁 - 轻量级锁(自旋锁(无锁)、自适应自旋) - 重量级锁
#锁升级过程
1.无锁态 :存储对象的hashcode
2.偏向锁 :当只有一个线程调用时,会将线程指针存在对象的markword中
3.轻量级锁(自旋锁): 当出现资源的竞争关系时,就会取消偏向锁,线程栈中生成Lock Record指针,抢占对象资源,将Lock Record存入markword中,一直自旋,直到获取到资源,占用cpu
4.重量级锁(互斥锁): 向操作系统申请锁资源,会将占用资源外的其他线程放入队列等待,不占用cpu资源
锁消除
Public void add(String s1, String s2){
StringBuffer s = new StringBuffer();
s.append(s1).append(s2);
}
StringBuffer在add方法内部,不会被线程共享,jvm会自动消除StringBuffer中的锁,提高cpu效率
锁粗化
StringBuffer s = new StringBuffer();
String str = "abc";
int i = 0;
while(i < 100){
s.append(str);
i++;
}
jvm检测到一连串的操作都对同一个对象加锁,就会将锁粗化到外部,while循环
Synchronized实现过程
1.java层面 :synchronized
2.编译字节码:monitorenter开启监视器 monitorexit退出监视器
3.锁自动升级
4.lock comxchg
AQS原理
AbstractQueuedSynchronizer类
抽象的同步队列 ==> 解决数据安全问题
核心思想
AQS的核心思想是如果当前线程请求的共享资源是可用的,则让线程操作当前共享资源,并将共享资源进行锁定
如果当前线程请求的共享资源是锁定状态,则将线程保存到阻塞队列
AQS使用了双向链表保存阻塞队列
state变量保存锁的状态,0为资源可用,1为资源锁定 >1为重入锁
**重入锁**,当前获取锁的线程再次获取锁,state+1
state的改变在原子性的基础上进行改变
Threadlocal
使用完Threadlocal后需要remove,不然会造成内存泄漏
ThreadLocalMap的key为ThreadLocal对象 ThreadLocal采用弱引用,当ThreadLocal使用完制空时,value还存在,就会造成内存泄露
所以在使用完ThreadLocal之后要进行remove
ThreadLocal三个方法 set get remove
ThreadLocal是本地线程变量,在当前线程中是对其他线程隔离的,ThreadLocal可以为线程创建共享变量的副本,比如多个线程同时操作一个单例对象中的成员变量,可以使用ThreadLocal,就不会造成混乱
使用场景
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。
volatail
volatail可以保证变量的可见性
JMM内存模型中,每个线程都会生成自己的内存空间,对共享变量都会复制一个副本进行操作,所以会导致数据不能即时更新
volatail保证数据的可见性,当共享数据被改变时,所有副本都能看到并作出改变,但volatail不能保证变量的原子性,因为数据操作指令可能是多条
Session会话管理。
## volatail
```bash
volatail可以保证变量的可见性
JMM内存模型中,每个线程都会生成自己的内存空间,对共享变量都会复制一个副本进行操作,所以会导致数据不能即时更新
volatail保证数据的可见性,当共享数据被改变时,所有副本都能看到并作出改变,但volatail不能保证变量的原子性,因为数据操作指令可能是多条