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

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());
		}
}

Untitled

然后可以看到打印输出,当然这里因为对齐方式,我们看到的其实顺序是反过来的,按最后三位去看,我们这是 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());
		}
} 

https://gitee.com/nicksxs/images/raw/master/uPic/2LBKpX.jpg

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

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());
        }
		}
}

看下运行结果

https://gitee.com/nicksxs/images/raw/master/uPic/V2l78m.png

可以看到是加上了 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();
		}
}

https://gitee.com/nicksxs/images/raw/master/uPic/bRMvlR.png

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

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;
}

https://gitee.com/nicksxs/images/raw/master/uPic/LMzMtR.png

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