Java教程

Java_锁

本文主要是介绍Java_锁,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

对象在内存的存储布局

因为锁是存储在对象头里面,所以我们先看下对象的存储结构。

对象在内存的存储布局.jpg

Java 锁

锁是记录在对象头信息中的, Synchronized 修饰的进行加锁的过程

请看代码:

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

        Object o = new Object();

        System.out.println(ClassLayout.parseInstance(o).toPrintable());

        synchronized (o){
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }

    }
}

输出结果:
发现加锁之后对象头信息发生了变化,证明所所信息是存储到对象头中的。
717113955146.png

锁升级的过程

使用 synchronized 关键字不是一开始就是加重量级锁的,是根据情况一步步升级的,不同级别对系统的资源小号是不一样的,只有必要情况才会升级到重量级锁 “Synchronized” 的,以下是锁升级的过程图。

锁升级.jpg

锁升级过程: new - 偏向锁-轻量级锁(无锁、自旋锁、自适应锁)- 重量级锁

无锁态:

通过 new 关键字创建对象

偏向锁:

  • 升级过程:
    • 当线程调用这个对象时,发现这个对象没有被任何线程污染过,我是第一个拥有这个对象的线程。我就不会给它太重量级的锁,会给当前对象贴一个标签,锁升级为偏向锁,下次对象再次调用发现还是这个标签,无需上锁,就直接调用了。
  • markwork 存储值:
    • 指向当前线程的指针【标签】,hashcode 会被存储到线程栈中 ,他会把mark word备份到自己的线程栈。

轻量级锁:

  • 升级过程:

    • 出现竞争的时候,会由偏向锁升级为轻量级锁。当有其他线程再次调用这个对象时,发现当前对象指针不是自己的,撤销偏向锁,撕除标签【去除线程指针】。
  • 线程抢夺锁:

    • 每个线程都有自己的线程栈,在自己的线程栈生成一个自己的对象【Lock Record】
    • 看谁能把自己的 Lock Record 贴到对象上,谁就拥有这把锁。抢的过程,通过自旋(CAS) 的方式抢夺,读取对象中的 Lock Record,并且判断是否可以修改,在回写本线程的Lock Record 指针到对象时,判断是不是自己取的那个值。
  • markwork 存储值:

    • 线程栈中 Lock Record 的指针,hashcode 会被存储到线程栈中 ,他会把mark word备份到自己的线程栈。
  • 升级原因:

    • 轻量级锁太消耗资源了【因为不断自旋】。
  • 升级策略:

    • 在之前的jdk: 超过十次自旋,或者自旋的线程占超过CPU核数的一半,升级为最终的重量级锁。
    • 在 jdk6 以后,加入自适应自旋 Adapative Self Spining,JVM自己控制。
  • 升级过程:

    • 升级重量级锁 -> 向操作系统申请资源,获得 Linux mutex【锁】。CPU从3级【用户态】-0级【内核态】调用,线程挂起,进入等待队列,等待操作系统的调度,然后映射到用户空间。
  • markwork 存储值:

    • 指向互斥量(重量级锁mutex )的指针。

小知识:

  • 整个程序的执行状态分为
    • 用户态,内核态。内核态是非常核心的跟内核、硬件打交道的操作只有它能执行。【比如往网卡、显卡上写数据。】
  • 分代年龄
    • 一个对象被垃圾回收器回收一次,年龄会+1 ,年龄到达一定程度,这个对象会从 “年轻代” 升级到 ”老年代“。分代年龄在 JVM 里面是可以通过参数控制的,分代年龄两种默认值,第一种模式 15 ,PS+PO回收器 ;第二种模式 6 ,使用 cms 回收器 。4 Bit最大值15。
  • synchronized
    • 重量级锁,需要向操作系统申请,操作系统的锁个数是一定的。
  • 偏向锁
    • 更偏向于第一个调用它的线程。
    • 默认开启,可以关闭。

升级过程笔记截图:

来自于马士兵

升级详细过程笔记.jpg

锁销除 lock eliminate

public void add(String str,String str2){
        StringBuffer sb = new StringBuffer();
        sb.append(str).append(str2);
}

StringBuffer 是线程安全的,因为它的关键方法都是被 synchronized 修饰过的,但是发现上面的代码,sb 这个引用只会在 add 方法中使用,不可能被其他线程引用 (因为是局部变量,栈私有),因此 sb 是不可能共享的资源,JVM 会消除 StringBuff 对象内部的锁。

锁粗化 lock coarsening

    public void test() {
        int i = 0;
        //在锁粗化之前运行逻辑如下列代码
        while(i < 100) {
            synchronized (this) {
            }
            i++;
        }

        //在锁粗化之后运行逻辑如下列代码
        synchronized (this) {
            while(i < 100) {
                i++;
            }
        }
    }

JVM 会检测到这样一连串的操作都对同一个对象加锁(while 循环内 100 次执行 append ,没有锁粗化的就要进行 100 次加锁/解锁 ),此时 JVM 就会将加锁的范围粗化到这一连串的操作的外部(比如 while 循环体外),使得这一连串操作只需要加一次锁即可。

Synchronized 最底层实现

JIT (Just In Time Compiler ) 即时编译

首先了解一下 JVM 的即时编译,对于热点代码直接编译成机器语言,提高效率。
JIT 会把热点代码编译成汇编语言

public class T2 {
    public static void main(String[] args) {
        for (int i = 0; i < 1000000; i++) {
            m();
            n();
        }
    }
    
    public static synchronized void m(){

    }

    public static void n(){

    }
}

m() , n() 执行了一百万次,所以这两个方法在这个程序中就叫热点代码,把热点代码进行即时编译,编译成机器语言,到第10次编译成机器语言了,可能到第11次执行的时候就不再重新解释了,就直接执行了,效率会高很多。

通过命令查看:

java -XX:+UnlockDiagnosticVMOptions -XX:PrintAssembly T2

C1 Compile Level 1 (一级优化)

C2 Compile Level 2 (二级优化)

JIT 会把 m(),n() 编译成汇编码,会看到 lock comxchg …指令。

synchronized 实现过程:

  1. Java代码层面:添加 synchronized 关键字
  2. Java字节码层面:监视器 moniterenter 、 moniterexit
  3. 执行过程中自动升级
  4. lock comxchg

ThreadLocal

变量。。。。 跟自己线程绑定的。

创建 ThreadLocal 线程

public void Test03(){
        ThreadLocal t = new ThreadLocal();
        t.set(new Object());
        t.remove(); //防止内存泄漏
}

查看 set 方法代码

   public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

	//getMap(t);
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
	
 // t.threadLocals;
 ThreadLocal.ThreadLocalMap threadLocals = null;

这篇关于Java_锁的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!