synchronized & volatile
在Java内存模型中,保证并发安全的三个特性:原子性、有序性、可见性。
CAS
compare and swap
compare and exchange
比较在交换
乐观锁的思想,是一种无锁算法。
jdk5后增加的并发包下java.util.concurrent.*下面的类使用了CAS算法实现。
例如AtomicInteger中的一个方法:

自旋 do while
有三个参数,内存值A、旧的预期值B、新的预期值C,
当且仅当 A == B的时候,将A更新为C。
ABA问题:
当前线程在CAS过程中,进行比较的时候,发现值还是原来的值,但是是经过别的线程操作之后的值,并不是初始的值。
其他线程修改数次后,最终值与初始值相等。
举个不太恰当的栗子:
小明与女友分手了,半年后又和好了,但是在这半年内,虽然还是那个人,他并不知道他女友有没有跟其他人交往过。
解决:加版本号、时间戳、或者加Boolean类型标记。
CAS在硬件级指令
cmpxchg 前加lock(多个cpu)使这条指令为原子操作,即
lock cmpxchg 指令
描述对象在内存中的内存布局
JOL 是个工具
IDEA插件 :https://plugins.jetbrains.com/plugin/10953-jol-java-object-layout
布局:
markword 锁的信息都在这里 8字节
类型指针 class pointer 指向属于哪个类 4字节
实例数据 instance data 成员变量所在
对齐 padding 整体字节数不能被8整除的时候,补齐
(loss due to the next object alignment)
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
执行上面代码,
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
新生成一个Object对象,占用的内存情况:
Mark Word 8字节
Class Pointer 4字节
Instance Data 0
对齐 4字节
总共16个字节。
UseCompressedClassPointer
class pointer 压缩指针
64位JVM,它的指针长度就是64位,8个字节
开启压缩指针后会压缩为4个字节、
UseCompressedOops
oops ordinary object pointers 普通对象指针,默认也是压缩的,4字节
锁升级过程
全部记录在markword中。
new - 偏向锁 - 轻量级锁(无锁、自旋锁、自适应锁)- 重量级锁
第一次加锁的时候,第一个线程,对象中的markword还没有被污染,所以第一次加的锁是偏向锁(54位指向当前线程的指针),不用去操作系统申请重量级锁。
发生任意竞争的时候,升级为轻量级锁,
首先撤销偏向锁状态,
每个线程的线程栈生成自己的锁记录,lock record
mark word中指向线程栈中的Lock Record的指针
使用自旋的方式去抢夺锁 CAS操作
竞争激烈的时候,
自旋锁太费CPU,锁升级为重量级锁
1、自旋超过多少次,或者是超过CPU的二分之一
2、自适应自旋,JVM自己控制
用户态、内核态
用户线程想要操作硬件的时候需要内核线程完成
重量级锁 队列
锁降级
在GC的情况下发生,没有意义
锁消除
StringBuffer append一堆,锁会消除
锁粗化
volatile
内存可见性和禁止指令重排序
看一个例子
Boolean running = true;
void m(){
System.out.println(" m start ");
while (running){
// nothing
}
System.out.printf(" m end ");
}
public static void main(String[] args) {
HelloVolatile t = new HelloVolatile();
new Thread(t :: m).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running = false;
}
以上这个例子,本意是有一个变量running,初始值为true,然后启动了一个线程,while(running)循环,主线程在一秒后修改了这个running变量值为false,预期结果是我们新启动的线程也会停止掉。
结果显然不是这样的。因为每个线程都有自己的工作内存,当线程启动时,会将变量复制到自己的工作内存中,虽然我们在主线程中修改了变量值,但是对于新启动的线程的工作内存是不可见的。所以启动的新线程会一直运行。
将running变量修饰为volatile即可出现预期情况。
某个线程修改了变量值后,会通知到主内存该值已被修改,同时其他线程持有的该变量失效,重新从主内存中读取。
CPU三级缓存、主内存、线程的工作内存
8个操作
MESI缓存一致性协议
Modify
Exclusive
Shared
Invalid
如何禁止指令重排序
对象的创建过程
new
invokespecial -- init
astore_1
return
1、代码中变量用volatile修饰
2、在编译为字节码文件后,该属性的acc_flags 是ACC_VOLATILE
3、JVM的内存屏障,虚拟机看到ACC_VOLATILE后,屏障两边的指令不可以重排,保障有序
LoadLoad
StoreStore
LoadStore
StoreLoad
4、hotspot实现
lock
强软弱虚
强引用 Object o = new Object(); 当没有任何引用指向该对象内存的时候,才会被垃圾回收
软引用 适合做缓存,当内存不够用的时候,会优先回收软引用的对象。
弱引用 gc的时候会直接被回收,一次性,当加载的时候会用到
虚引用 get不到。作用:管理堆外内存。NIO中 DirectByreBuffer
ThreadLocal
每个线程独自拥有
DCL 单例模式
缓存行 cache line
举个栗子:
public static class T{
private long a1,a2,a3,a4,a5,a6,a7;
public volatile long x = 0L;
}
public static T[] arr = new T[2];
static {
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 0; i < 10000000; i++) {
arr[0].x = i;
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 10000000; i++) {
arr[1].x = i;
}
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(System.currentTimeMillis() - start);
}
类T中有7个long类型的变量是没有用到的,但是如果把这7个变量去掉,执行的时间会大大增加。
去掉后 执行时间 大概200多毫秒
没有去掉 执行时间 大概是 80多毫秒
这是因为缓存行的大小一般是64字节,CPU与内存交换数据中是以缓存行为单位的;多了7个long类型的变量,数组中的两个元素不在同一个缓存行上,两个线程就不用相互通知对方进行数据修改。
markword中记录了哪些信息?
4字节 分代年龄 最大15
CMS辣鸡回收器 年龄为6后转入老年代
面试题
Q:请描述synchronized 和reentrantlock的底层实现及重入的底层原理
Q:请描述锁的四种状态和升级过程
Q: CAS的ABA问题如何解决
Q:请谈一下你对volatile的理解
Q: volatile的可见性和禁止指令重排序是如何实现的
Q: CAS是什么
Q: 请描述一下对象的创建过程
Q:对象在内存中的内存布局
Q:DCL单例为什么要加volatile
Q: 解释一下锁的四种状态
Q: Object o = new Object()在内存中占了多少字节
Q:请描述synchronized和RenntrantLock的异同
Q:聊聊你对as-if-serial和happens-brfore语义的理解
Q:你了解ThreadLocal吗,你知道ThreadLocal如何解决内存泄露问题吗
Q:请描述下锁的分类以及JDK中的应用
HashTable和CurrentHashMap的线程安全实现的区别
Copyright © 2015 Powered by MWeb, Theme used GitHub CSS.
