Java内存模型:JMM(Java Memory Model )
为了解决相同代码在不同操作系统上出现的各种问题,引入了JMM来屏蔽不同的硬件、操作系统带来的差异
JMM规定所有的变量都会存储在主内从中,操作时,需要复制一份到线程内存(CPU内存),在线程内部做计算,然后再写会主内存(不一定会及时操作)。
原子性:一个不可分割、不可中断的操作,一个线程在执行时其他线程不会影响他
// 以下代码用于测试count++的操作时非原子性操作,多线程同时操作的时候,总数比200小
private static int count;
public static void increment(){
Thread.sleep(10);
count++;
}
public static void main(String[] args){
Thread t1 = new Thread(()->{
for(int i=0;i<100;i++){
increment();
}
})
Thread t2 = new Thread(()->{
for(int i=0;i<100;i++){
increment();
}
})
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
++的操作,先从主内存拿到数据保存到cpu的寄存器中,在寄存器中进行+1操作,之后再写回主内存中。
如何保证并发编程的原子性
-
可以在方法上使用synchorinized关键字或使用同步代码块的方式。monitorenter与monitorexit实现锁资源的竞争,保证同一时刻只会有一个线程进入操作
-
CAS(compare and swap)比较和交换,是CPU的一条原语,他在替换内存的某个值时,首先查看内存中的值是否与预期值一致,如果一致则执行替换操作。在Java中提供了Unsafe类实现CAS的操作方法。
// 使用java提供的CAS方式实现原子性操作 private static AtomicInteger count = new AtomicInteger(0); public static void increment(){ Thread.sleep(10); count.incrementAndGet(); } public static void main(String[] args){ Thread t1 = new Thread(()->{ for(int i=0;i<100;i++){ increment(); } }) Thread t2 = new Thread(()->{ for(int i=0;i<100;i++){ increment(); } }) t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count); }
CAS的缺点:
- 只能保证对一个变量的操作时原子性的,无法实现对多行代码实现原子性。
CAS的问题:
ABA问题,可以通过引入版本号的方式解决ABA问题,java中提供了一个类在CAS操作时通过追加版本号操作:AtomicStampedReference
引入AtomicStampedReference解决ABA问题
自旋时间过长问题:
-
可以指定CAS一共循环多少次,如果超过次数则直接失败/挂起线程
-
可以在CAS一次失败后,将这个操作暂存起来,后续获取结果时,将暂存的结果全部执行完,再返回最后的结果。
Lock锁,在并发数多时,使用Reentrantlock性能会好一些。Reentrantlock 底层基于AQS实现,有一个基于CAS维护的变量state 来实现锁的操作
private static Reentrantlock lock = new Reentrantlock();
lock.lock()
// 执行逻辑
lock.unLock();
ThreadLocal保证原子性的方式是不让多线程去操作临界资源,让每个线程去操作属于自己的数据。
实现原理:
-
每个ThreadLocal中存储着一个变量:ThreadLocalMap
-
ThreadLocal本身不存储数据,而是一个工具类,提供了操作ThreadLocalMap的方法
-
ThreadLocal本身就是基于Entry[]实现的,一个线程可以存储多个ThreadLocal
-
每一个线程都有自己的ThreadLocalMap,再基于ThreadLocal作为key,对value进行存取,ThreadLocalMap与ThreadLocal之间是弱引用,即若tl1引用不存在的情况下,ThreadLocal1对象会被GC回收
ThreadLocal内存泄漏问题