Synchronized 的底层原理——Java全栈知识(40)

Synchronized 的底层原理

1、Synchronized 如何使用?

我们都知道 Synchronized 关键字会给代码上互斥锁,但是上锁的位置分为两种,分别是对象锁和类锁

1.1、对象锁

1、Synchronized 默认锁的对象是 this,也可以手动指定锁对象。
案例一(锁 this 对象):

public class SynchronizedObjectLock implements Runnable {  
    static SynchronizedObjectLock instance = new SynchronizedObjectLock();  
  
    @Override  
    public void run() {  
        // 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行  
        synchronized (this) {  
            System.out.println("我是线程" + Thread.currentThread().getName());  
            try {  
                Thread.sleep(3000);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            System.out.println(Thread.currentThread().getName() + "结束");  
        }  
    }  
  
    public static void main(String[] args) {  
        Thread t1 = new Thread(instance);  
        Thread t2 = new Thread(instance);  
        t1.start();  
        t2.start();  
    }  
}

运行结果是:


我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

[!note]

1、我们可以看到 t 1 运行时锁住了 instance 对象

2、t 2 开始运行获取当前对象的锁

3、阻塞等待到 t 1 释放锁,t 2 获取锁执行。

2、Synchronized 指定锁对象

案例二(指定锁对象):

public class SynchronizedObjectLock implements Runnable {  
    static SynchronizedObjectLock instance = new SynchronizedObjectLock();  
    // 创建2把锁  
    Object block1 = new Object();  
    Object block2 = new Object();  
  
    @Override  
    public void run() {  
        // 这个代码块使用的是第一把锁,当他释放后,后面的代码块由于使用的是第二把锁,因此可以马上执行  
        synchronized (block1) {  
            System.out.println("block1锁,我是线程" + Thread.currentThread().getName());  
            try {  
                Thread.sleep(3000);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            System.out.println("block1锁,"+Thread.currentThread().getName() + "结束");  
        }  
  
        synchronized (block2) {  
            System.out.println("block2锁,我是线程" + Thread.currentThread().getName());  
            try {  
                Thread.sleep(3000);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            System.out.println("block2锁,"+Thread.currentThread().getName() + "结束");  
        }  
    }  
  
    public static void main(String[] args) {  
        Thread t1 = new Thread(instance);  
        Thread t2 = new Thread(instance);  
        t1.start();  
        t2.start();  
    }  
}

block1锁,我是线程Thread-0  
block1锁,Thread-0结束  
block2锁,我是线程Thread-0  // 可以看到当第一个线程在执行完第一段同步代码块之后,第二个同步代码块可以马上得到执行,因为他们使用的锁不是同一把  
block1锁,我是线程Thread-1  
block2锁,Thread-0结束  
block1锁,Thread-1结束  
block2锁,我是线程Thread-1  
block2锁,Thread-1结束

[!note]

1、t 1 线程获取到 block 1,t 2 线程阻塞等待

2、t 1 线程释放 block 1 获取到 block 2,t 2 获取到 block1

3、t 1 线程释放 block 2 ,t 2 线程获取到 block 2 执行

3、方法锁:synchronized 修饰普通方法,锁对象默认为 this

案例三:

public class SynchronizedObjectLock implements Runnable {  
    static SynchronizedObjectLock instance = new SynchronizedObjectLock();  
  
    @Override  
    public void run() {  
        method();  
    }  
  
    public synchronized void method() {  
        System.out.println("我是线程" + Thread.currentThread().getName());  
        try {  
            Thread.sleep(3000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println(Thread.currentThread().getName() + "结束");  
    }  
  
    public static void main(String[] args) {  
        Thread t1 = new Thread(instance);  
        Thread t2 = new Thread(instance);  
        t1.start();  
        t2.start();  
    }  
}

运行结果是:


我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

1.2、类锁

指 synchronize 修饰静态的方法或指定锁对象为 Class 对象

1、 synchronize 修饰静态方法

示例一(修饰普通方法):

public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        method();
    }

    // synchronized用在普通方法上,默认的锁就是this,当前实例
    public synchronized void method() {
        System.out.println("我是线程" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束");
    }

    public static void main(String[] args) {
        // t1和t2对应的this是两个不同的实例,所以代码不会串行
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
    }
}

输出结果:

我是线程Thread-0
我是线程Thread-1
Thread-1结束
Thread-0结束
  • 示例2(synchronized 修饰静态方法)
public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        method();
    }

    // synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把
    public static synchronized void method() {
        System.out.println("我是线程" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
    }
}

输出结果:

我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

[!note]

synchronized 用在静态方法上,默认的锁就是当前所在的 Class 类,所以无论是哪个线程访问它,需要的锁都只有一把

2 、 synchronized 修饰 Class 对象

public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        // 所有线程需要的锁都是同一把
        synchronized(SynchronizedObjectLock.class){
            System.out.println("我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
    }
}

输出结果:

我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

[!note]

synchronized (SynchronizedObjectLock. class)锁的是当前类。

2、 synchronized 的原理

2.1、Monitor

Monitor 被翻译为监视器,是由 jvm 提供,c++语言实现
在代码中想要体现monitor需要借助javap命令查看clsss的字节码,比如以下代码:

public class SyncTest {  
​  
    static final Object lock = new Object();  
    static int counter = 0;  
    public static void main(String[] args) {  
        synchronized (lock) {  
            counter++;  
        }  
    }  
}

找到这个类的class文件,在class文件目录下执行javap -v SyncTest.class,反编译效果如下:
image-20230504165342501

monitorenter 上锁开始的地方
monitorexit 解锁的地方
其中被 monitorenter 和 monitorexit 包围住的指令就是上锁的代码
有两个monitorexit的原因,第二个monitorexit是为了防止锁住的代码抛异常后不能及时释放锁

在使用了synchornized代码块时需要指定一个对象,所以synchornized也被称为对象锁
monitor主要就是跟这个对象产生关联,如下图
image-20230504165833809

[!note]
上面我们说了,synchronized 锁的是对象,所以说一个锁对应一个对象对应一个 Monitor

Monitor内部具体的存储结构:

  • Owner:存储当前获取锁的线程的,只能有一个线程可以获取
  • EntryList:关联没有抢到锁的线程,处于Blocked状态的线程
  • WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程

具体的流程:

  • 代码进入synchorized代码块,先让lock(对象锁)关联的monitor,然后判断Owner是否有线程持有
  • 如果没有线程持有,则让当前线程持有,表示该线程获取锁成功
  • 如果有线程持有,则让当前线程进入entryList进行阻塞,如果Owner持有的线程已经释放了锁,在EntryList中的线程去竞争锁的持有权(非公平)
  • 如果代码块中调用了wait()方法,则会进去WaitSet中进行等待

总结:

  • Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】
  • 它的底层由monitor实现的,monitor是jvm级别的对象( C++实现),线程获得锁需要使用对象(锁)关联monitor
  • 在monitor内部有三个属性,分别是owner、entrylist、waitset
  • 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程
Javasynchronized底层原理是基于对象监视器(Object Monitor)实现的。 每个Java对象都有一个与之关联的监视器锁(Monitor Lock),也称为内置锁(Intrinsic Lock)或互斥锁(Mutex Lock)。当一个线程试图进入一个synchronized方法或代码块时,它会尝试获取该方法或代码块所关联对象的监视器锁。 当一个线程成功获取锁后,它可以执行同步代码。其他线程如果想要获取同一个锁,就必须等待该线程释放锁。这样可以确保同一时间只有一个线程能够执行被锁定的代码,从而实现了线程安全。 底层实现中,监视器锁的获取和释放是通过底层操作系统的互斥原语来实现的。具体地说,当一个线程获取锁时,它会尝试将对象的监视器锁标记为已占用。如果锁已经被其他线程占用,那么获取锁的线程就会被阻塞,直到锁被释放。 在Java虚拟机中,对象监视器的实现通常包括一个等待队列(Waiting Queue)和一个通知队列(Notification Queue)。等待队列用于存放那些尝试获取锁但未成功的线程,而通知队列用于存放那些等待被唤醒的线程。 当一个线程释放锁时,它会将锁的状态设置为可用,并且从等待队列中选择一个线程进行唤醒,使其有机会获取锁。被唤醒的线程会进入到就绪状态,然后与其他线程竞争获取锁。 总之,Javasynchronized底层原理是通过对象监视器实现的,依赖于操作系统提供的互斥原语来实现线程同步和互斥访问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

东莞呵呵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值