Synchronized底层实现

阅读此文你应该具备以下知识:

synchronized是什么

如果某一个资源被多个线程共享,为了避免因为资源抢占导致资源数据错乱,我们需要对线程进行同步,在Java中,synchronized 就是实现线程同步的关键字。

使用 synchronized 关键字,拿到 Java 对象的锁,保护锁定的代码块。JVM 保证同一时刻只有一个线程可以拿到这个 Java 对象的锁,执行对应的代码块,从而达到线程安全。

synchronized的使用

synchronized关键字可以用来修饰三个地方:

  • 修饰实例方法上,锁对象是当前的 this 对象。
  • 修饰代码块,也就是synchronized(object){},锁对象是()中的对象,一般为this或明确的对象。
  • 修饰静态方法上,锁对象是方法区中的类对象,是一个全局锁。
  • 修饰类,即直接作用一个类。

针对synchronized修饰的地方不同,实现的原理不同。

synchronized修饰实例方法

public class SyncTest {

    public synchronized void sync(){
		
    }
}

通过javap -verbose xxx.class查看反编译结果:

image-20210917192327640

从反编译的结果来看,我们可以看到sync()方法中多了一个标识符。JVM就是根据该ACC_SYNCHRONIZED标识符来实现方法的同步,即:

当方法被执行时,JVM 调用指令会去检查方法上是否设置了ACC_SYNCHRONIZED标识符,如果设置了ACC_SYNCHRONIZED标识符,则会获取锁对象的 monitor 对象,线程执行完方法体后,又会释放锁对象的 monitor对象。在此期间,其他线程无法获得锁对象的 monitor 对象。

synchronized修饰代码块

public class SyncTest {

    private static int count;

    public SyncTest() {
        count = 0;
    }

    public void sync() {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }


    public static void main(String[] args) {
        SyncTest s = new SyncTest();
        Thread t0 = new Thread(new Runnable() {
            @Override
            public void run() {
                s.sync();
            }
        });
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                s.sync();
            }
        });
        t0.start();
        t1.start();
    }
}

输出:

Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-1:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9

很明显,线程 1 要等到线程 0 执行完之后才会开始执行。再去查看字节码信息:

image-20210917193128458

我们可以看到sync()字节码指令中会有两个monitorentermonitorexit指令:

  • monitorenter: 该指令表示获取锁对象的 monitor 对象,这时 monitor 对象中的 count 会加+1,如果 monitor 已经被其他线程所获取,该线程会被阻塞住,直到 count = 0,再重新尝试获取monitor对象。

  • monitorexit: 该指令表示该线程释放锁对象的 monitor 对象,这时monitor对象的count便会-1变成0,其他被阻塞的线程可以重新尝试获取锁对象的monitor对象。

synchronized修饰静态方法

public class SyncTest {

    private static int count;

    public SyncTest() {
        count = 0;
    }

    public synchronized static void sync() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    public static void main(String[] args) {
        SyncTest s0 = new SyncTest();
        SyncTest s1 = new SyncTest();
        Thread t0 = new Thread(new Runnable() {
            @Override
            public void run() {
                s0.sync();
            }
        });
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                s1.sync();
            }
        });
        t0.start();
        t1.start();
    }
}

测试结果:

Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-1:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9

我们知道静态方法是属于类的而不属于对象的。同样,synchronized 修饰的静态方法锁定的是这个类的所有对象。因此,尽管是s0s1是2个不同的对象,但在t1t2并发执行时却保持了线程同步,就是因为sync()是静态方法,而静态方法是属于类的,所以s0s1相当于用了同一把锁。

再去看看字节码信息:

image-20210917203701163

可以看到跟放在实例方法相同,也是sync()方法上会多一个标识符。可以得出synchronized放在实例方法上和放在静态方法上的实现原理相同,都是ACC_SYNCHRONIZED标识符去实现的。只是它们锁住的对象不同。

synchronized修饰类

public class SyncTest {

    private static int count;

    public SyncTest() {
        count = 0;
    }

    public void sync() {
        synchronized (SyncTest.class) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThr
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LBXX_1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值