Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

# 聊聊 Java 中绕不开的 Synchronized 关键字-二

发表于 2021-06-27 更新于 2022-06-11 分类于 Java 阅读次数： 阅读次数： Disqus：

# Java并发

synchronized 的一些学习记录

jdk1.6 以后对 synchronized 进行了一些优化，包括偏向锁，轻量级锁，重量级锁等

这些锁的加锁方式大多跟对象头有关，我们可以查看 jdk 代码

首先对象头的位置注释

    // Bit-format of an object header (most significant first, big endian layout below):
    //
    //  32 bits:
    //  --------
    //             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
    //             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
    //             size:32 ------------------------------------------>| (CMS free block)
    //             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
    //
    //  64 bits:
    //  --------
    //  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
    //  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
    //  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
    //  size:64 ----------------------------------------------------->| (CMS free block)
    //
    //  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
    //  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
    //  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
    //  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

    enum { locked_value             = 0,
             unlocked_value           = 1,
             monitor_value            = 2,
             marked_value             = 3,
             biased_lock_pattern      = 5
    };

我们可以用 java jol库来查看对象头，通过一段简单的代码来看下

    public class ObjectHeaderDemo {
        public static void main(String[] args) throws InterruptedException {
            L l = new L();
            System.out.println(ClassLayout.parseInstance(l).toPrintable());
    		}
    }

然后可以看到打印输出，当然这里因为对齐方式，我们看到的其实顺序是反过来的，按最后三位去看，我们这是 001，好像偏向锁都没开，这里使用的是 jdk1.8，默认开始偏向锁的，其实这里有涉及到了一个配置，jdk1.8 中偏向锁会延迟 4 秒开启，可以通过添加启动参数 -XX:+PrintFlagsFinal，看到

因为在初始化的时候防止线程竞争有大量的偏向锁撤销升级，所以会延迟 4s 开启

我们再来延迟 5s 看看

    public class ObjectHeaderDemo {
        public static void main(String[] args) throws InterruptedException {
    				TimeUnit.SECONDS.sleep(5);
            L l = new L();
            System.out.println(ClassLayout.parseInstance(l).toPrintable());
    		}
    }

可以看到偏向锁设置已经开启了，我们来是一下加个偏向锁

    public class ObjectHeaderDemo {
        public static void main(String[] args) throws InterruptedException {
            TimeUnit.SECONDS.sleep(5);
            L l = new L();
            System.out.println(ClassLayout.parseInstance(l).toPrintable());
            synchronized (l) {
                System.out.println("1\n" + ClassLayout.parseInstance(l).toPrintable());
            }
            synchronized (l) {
                System.out.println("2\n" + ClassLayout.parseInstance(l).toPrintable());
            }
    		}
    }

看下运行结果

可以看到是加上了 101 = 5 也就是偏向锁，后面是线程 id

当我再使用一个线程来竞争这个锁的时候

    public class ObjectHeaderDemo {
        public static void main(String[] args) throws InterruptedException {
            TimeUnit.SECONDS.sleep(5);
            L l = new L();
            System.out.println(ClassLayout.parseInstance(l).toPrintable());
            synchronized (l) {
                System.out.println("1\n" + ClassLayout.parseInstance(l).toPrintable());
            }
    				Thread thread1 = new Thread() {
                @Override
                public void run() {
                    try {
                        TimeUnit.SECONDS.sleep(5L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (l) {
                        System.out.println("thread1 获取锁成功");
                        System.out.println(ClassLayout.parseInstance(l).toPrintable());
                        try {
                            TimeUnit.SECONDS.sleep(5L);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
    				thread1.start();
    		}
    }

可以看到变成了轻量级锁，在线程没有争抢，只是进行了切换，就会使用轻量级锁，当两个线程在竞争了，就又会升级成重量级锁

    public class ObjectHeaderDemo {
        public static void main(String[] args) throws InterruptedException {
            TimeUnit.SECONDS.sleep(5);
            L l = new L();
            System.out.println(ClassLayout.parseInstance(l).toPrintable());
            synchronized (l) {
                System.out.println("1\n" + ClassLayout.parseInstance(l).toPrintable());
            }
            Thread thread1 = new Thread() {
                @Override
                public void run() {
                    try {
                        TimeUnit.SECONDS.sleep(5L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (l) {
                        System.out.println("thread1 获取锁成功");
                        System.out.println(ClassLayout.parseInstance(l).toPrintable());
                        try {
                            TimeUnit.SECONDS.sleep(5L);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            Thread thread2 = new Thread() {
                @Override
                public void run() {
                    try {
                        TimeUnit.SECONDS.sleep(5L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (l) {
                        System.out.println("thread2 获取锁成功");
                        System.out.println(ClassLayout.parseInstance(l).toPrintable());
                    }
                }
            };
            thread1.start();
            thread2.start();
        }
    }

    class L {
        private boolean myboolean = true;
    }

可以看到变成了重量级锁。