深入理解同步与锁

一、CAS:(Compare and swap)

作用:
在没有锁的状态下,保证多个线程对同一个值的更新

实现:
用户态一直循环直到修改成功
do{}while(修改成功);

在这里插入图片描述

ABA问题:(你和女朋友分手,之后她和别的男的在一起,之后又和你复合了)

更新值的过程中,其他线程修改了。

解决方法:
-1.给这个值添加一个版本号,每次被修改时增加版本号,当你修改时判断版本号是否和你当初读取的一样
-2.给这个值添加一个Boolean类型,标记是否被修改过。

本质是如何实现的(与synchronized,volatile底层实现一样)

jdk中unsafe调用native方法。
底层有直接的指令支持CAS:lock cmpxchg(lock表示,当执行cmpxchg指令时,不能被打断)

二、JOL(java object layout)

//pem.xml配置文件修改
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>
//打印对象布局
public class ObjectTest {
    public static void main(String[] args) {

        Object o = new Object();

        //查看Object对象布局
        System.out.println(ClassLayout.parseInstance(o).toPrintable());

    }
}

对象在内存中的内存布局

字段 大小
markword(对象头) 8字节
class pointer(类型指针) 4字节
instance data(实例数据) 如果对象为空,则为0
padding(填充符) 4字节(补充为8的整数倍)

java查看默认命令行参数

java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=132883072 -XX:MaxHeapSize=2126129152 -XX:+PrintCommandLineFl
ags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesInd
ividualAllocation -XX:+UseParallelGC
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

UseCompressedClassPointers: 会将8字节指针压缩为4字节(为什么8字节,因为操作系统64位)

下面对象内存大小是多少?

计算大小时,要查看是否开启指针压缩(UseCompressedClassPointers)。

Object o = new Object();

o引用:4字节;object对象16字节;总共20字节

markword(对象头) 8字节(锁标志(3),分代年龄(4bit),hashcode(31bit),unusedGC标记)
class pointer(类型指针) 4字节
instance data(实例数据) 如果对象为空,则为0
padding(填充符) 4字节(补充为8的整数倍)

synchronized锁升级

synchronized上锁会优化。
对象new(无锁) -》偏向锁(默认打开,偏于第一个使用的线程) -》轻量级锁(自旋锁,无锁) -》重量级锁

这些变化记录在markword。

查看对象头

public class ObjectTest {
    public static void main(String[] args) {

        Object o = new Object();

        //查看Object对象布局
        System.out.println(ClassLayout.parseInstance(o).toPrintable());

        synchronized (o){ //锁定对象
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }
}

没加锁之前
在这里插入图片描述
加锁之后
在这里插入图片描述

锁的信息保存在对象头中
对象头

过程:

当一个线程对无锁对象上锁时,直接将自己的线程指针写入对象头(偏向锁)。
当存在线程竞争锁时,撤销偏向锁,线程内部生成lockrecord对象,并将这
个对象指针贴到对象头中,并将hashcode保存到lockrecord(自旋锁,CAS操作)。
解决自旋锁等待问题,一定情况下(自旋时间长,JVM自己控制),升级为重量
级锁(将指向互斥量的指针写入对象头)。

锁降级:

GC时候会出现,没有意思(为啥?因为对象都要删除了)。

锁消除(lock eliminate):

public  void  add(String str1, String str2){
	/**
	 *StringBuilder与StringBuffer区别:
	 *StringBuffer线程安全,append方法是同步的
	 */
	StringBuffer str = new StringBuffer();
	str.append(str1).append(str2);  //str对象不是共享的资源,jvm会消除锁
}

锁粗化(lock coarsening):

public String test(String str){
	int i = 0;
	StringBuffer buffer = new StringBuffer();
	while(i < 10){
		buffer.append(str); //会将这里的锁添加到while之外
		i++;
	}

	return buffer.toString();
}

JIT(just in time compiler,即时编译)

synchronized实现过程

1.java 代码: synchronized
2.JVM指令: monitorenter monitorexit
3.执行过程中自动升级
4.机器指令: lock cmpxchg

三、超线程

一个ALU对应的多个PC寄存器的组合。当cpu执行线程2时,ALU直接去另一些寄存器中执行,不需要加载线程2的环境到线程1使用的寄存器中。

四、缓存

cpu有L1,L2缓存,如果多核,那么多个核共享L3。

五、cache line(64字节)

cpu读取内存的时候按块读取。
缓存行对齐,加速程序执行速度

没有考虑cache line

public class Cache1 {
    private static class T{
        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 (long i = 0; i < 1000_0000L; i++){
                arr[0].x = i;
            }
        });

        Thread t2 = new Thread(()->{
            for (long i = 0; i < 1000_0000L; i++){
                arr[1].x = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime() - start)/100_0000);
    }

}

考虑cache line

public class Cache2 {

    private static class Padding{
        public volatile  long a1,a2,a3,a4,a5,a6,a7;     //让数据不在同一个cache line中
    }

    private static class T extends  Padding{
        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 (long i = 0; i < 1000_0000L; i++){
                arr[0].x = i;
            }
        });

        Thread t2 = new Thread(()->{
            for (long i = 0; i < 1000_0000L; i++){
                arr[1].x = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime() - start)/100_0000);
    }
}

六、volatile

作用:

  • 保证变量在线程间的可见性
  • 禁止指令重排序(防止指令半初始化)
    创建对象时,jvm执行三步。1:堆上分配对象(并设为默认值),2:调用构造函数初始化,3:将初始化好的对象和栈上的引用对应起来。如果不禁止重排序,会发生2,3重排序导致错误。

底层如何实现数据一致性:

  • (MESI(Modified,Exclusive,Shared,Invalid) Cache一致性协议) Inter CPU,其他也有对应的。(一个cache line中一个数据改变,通知其他数据)
  • MESI不行,锁总线

系统底层如何保证有序性

  • 内存屏障(屏障两边的指令不可以重排序)
  • 锁总线

volatile如何解决指令重排序
1.代码中的volatile
2.字节码,加ACC_VOLATILE标志
3.JVM加内存屏障
4.hotspot直接把总线锁了(方便移植)。

Java屏障规则:
读读,读写,写读,写写之间可以加屏障

JVM实现细节:
写操作之前和之后加屏障,读操作之前和之后加屏障

七、强软,弱虚引用:

:栈上的对象引用和堆中内存对象对应,当栈上对象为空时,堆中对象被回收

Object ob = new Object();
ob = null;

:和强引用类似,但是堆内存对象中又包含一个对象,他们之间是弱引用。

作用:
常常用于缓存中。

SoftReference<byte[]> m = new SoftReference<>(new byte[100102410]);
m.get(); //获取对象地
System.gc(); //没有释放回收
如何释放:当堆内存空间不足时,会自动释放。

:类似软引用,但是即使引用在也可以直接回收。一次性使用

作用:
防止内存泄漏。Thread Local使用这个技术。

WeakReference<byte[]> m = new WeakReference<>(new byte[100102410]);
System.gc(); //释放回收

作用:
管理堆外内存,JVM使用堆外内存(zero copy)

当某个对象关联到堆外内存的时候,这个对象被回收时,堆外内存也要被回收。
PhantomReference<byte[]> m = new PhantomReference<>(new byte[100102410],QUEUE);
m.get(); //获取对象地,但是返回null

发布了193 篇原创文章 · 获赞 13 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u013919153/article/details/105438807