1 线程不安全演示

public class ThreadAndLockTest1 {
    private static int a = 0;
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch la = new CountDownLatch(2);
        for (int t = 0;t < 2;t++){
            new Thread(()->{
                for (int i = 0;i < 100000;i++) {
                    a++;
                }
                la.countDown();
            }).start();
        }
        la.await();
        System.out.println(a);
    }
}

如果线程安全,那么打印结果应该是200000

执行结果,发现不是期望的结果,说明线程不安全

149202

 

2 锁演示

上面代码加上锁(synchronized)之后

public class ThreadAndLockTest1 {
    private static int a = 0;
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch la = new CountDownLatch(2);
        for (int t = 0;t < 2;t++){
            new Thread(()->{
                synchronized (ThreadAndLockTest1.class){
                    for (int i = 0;i < 100000;i++) {
                        a++;
                    }
                }
                la.countDown();
            }).start();
        }
        la.await();
        System.out.println(a);
    }
}

执行结果,是期望的结果

200000

 

3 对象在内存里面的存储布局(Oracle的虚拟机)

  什么东西可以作为一把锁?在解释这个问题之前,先了解对象是由什么构成的?

 

3.1 它主要分为三个部分

对象头:对象头又包括两类:markword,class pointer,

实例数据:instance data,

对齐填充:padding

 

 

3.2 markword

  它的大小是8字节

1)哈希码、

2)GC年龄分代、

3)锁的信息

  锁状态标志

  线程持有的锁、

  偏向线程id

  偏向时间戳

 

3.3 使用JOL查看对象内存

https://www.cnblogs.com/jthr/p/15980849.html

 

4 查看上锁对象在内存中布局

上面我们知道了对象的组成和怎么去看对象中的布局,现在我们来看下对象在上锁前、上锁中、上锁后的变化

 

4.1 示例代码

public class JolTest {

    static class T{
    }
    
    public static void main(String[] args) {
        T o = new T();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        synchronized (o){
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }
}

 

4.2 执行结果

发现上锁后对象头中的markword发生的变化,解锁后又恢复了。

上锁实际上是在对象头上做了个标记

 

  在这里插入图片描述

 001代表无锁

 00代表轻量级锁