• 详解synchronized关键字及锁的基本概念


    在我们的Java体系中共有

    1. 乐观锁和悲观锁
    2. 独占锁和共享锁
    3. 互斥锁和读写锁
    4. 公平锁和非公平锁
    5. 可重入锁(ReentrantLock)
    6. 自旋锁(spinlock)
    7. 分段锁(segment)
    8. 锁升级(无锁|偏向锁|轻量级锁|重量级锁)
    9. 锁优化技术(锁粗化、锁消除)

     27a0304811d84dcdbbcb4bfd1e8e292c.jpeg

     

    而我们今天要讲的synchronized同步锁。就是一种典型的悲观锁。

    一,悲观锁

              下面用图解的方式看一下:

               1d866c8fc25243fd90dbc155e70a0cc1.png

     

        在Java语言中,synchronized就是典型的悲观锁

    二,synchronized同步锁

               Synchronized同步锁,简单来说,使用Synchronized关键字将一段代码逻辑,用一把锁给锁起来,只有获得了这把锁的线程才访问。并且同一时刻, 只有一个线程能持有这把锁, 这样就保证了同一时刻只有一个线程能执行被锁住的代码,从而确保代码的线程安全。

    1. 什么是锁

          锁就是并发机制中的一种概念,在多个线程需要访问同一个共享数据时,为了避免产生各类问题,出现了锁的概念,锁加在共享数据上时,就能够防止多个线程共同访问数据。Java中提供了各类的锁,应用在不同的场景下,可以让高并发程序的效率和稳定性得到显著提升。

    2. synchronized的关键字用法

    • 修饰实例方法:synchronized修饰实例方法, 则用到的锁,默认为this当前方法调用对象;
    • 修饰静态方法:synchronized修饰静态方法, 则其所用的锁,默认为Class对象;
    • 修饰代码块:synchronized修饰代码块, 则其所用的锁,是某个指定Java对象;

    3.实例

    下面我们来具体看一组程序:

    (1)修饰实例方法 

    1. public class Demo01 {
    2. public static void main(String[] args) {
    3. // 实例化一个对象
    4. DO fa = new DO();
    5. // 创建不同的线程1
    6. Thread thread01 = new Thread() {
    7. public void run() {
    8. // 使用相同的对象访问synchronized方法
    9. fa.doSth1();
    10. }
    11. };
    12. // 创建不同的线程2
    13. Thread thread02 = new Thread() {
    14. public void run() {
    15. // 使用相同的对象访问synchronized方法
    16. fa.doSth1();
    17. }
    18. };
    19. // 启动线程
    20. thread01.start();
    21. thread02.start();
    22. }
    23. }
    24. class DO {
    25. // 实例方法
    26. public synchronized void doSth1() {
    27. // 获取this锁,才能执行该方法
    28. }
    29. // 实例方法
    30. public void doSth2() {
    31. synchronized(this) {
    32. // 获取this锁,才能执行该代码块
    33. }
    34. }

    (2) 修饰静态方法

    1. public class Demo01 {
    2. public static void main(String[] args) {
    3. // 创建不同的对象(相同类型)
    4. Do a = new Do();
    5. Do b = new Do();
    6. // 创建不同线程1
    7. Thread thread01 = new Thread() {
    8. public void run() {
    9. // 使用不同的对象访问synchronized方法
    10. a.doSth2();
    11. }
    12. };
    13. // 创建不同线程2
    14. Thread thread02 = new Thread() {
    15. public void run() {
    16. // 使用不同的对象访问synchronized方法
    17. b.doSth2();
    18. }
    19. };
    20. // 启动线程
    21. thread01.start();
    22. thread02.start();
    23. }
    24. }
    25. class Do {
    26. // 实例方法
    27. public synchronized void doSth1() {
    28. // 获取this锁,才能执行该方法
    29. }
    30. // 实例方法
    31. public void doSth2() {
    32. synchronized(this) {
    33. // 获取this锁,才能执行该代码块
    34. }
    35. }
    36. }

    (3)修饰代码块

    synchronized(自定义对象) {
              //临界区
    }

    三, synchronized关键字补充

              

    • 当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

              在没有加锁的情况下, 所有的线程都可以自由地访问对象中的代码, 而synchronized关键字只            是限制了线程对于已经加锁的同步代码块的访问,并不会对其他代码做限制。所以,同步代             码块应该越短小越好。

    • 父类中synchronized修饰的方法,如果子类没有重写,则该方法仍然是线程安全性;如果子类重写,并且没有使用synchronized修饰,则该方法不是线程安全的;
    • 在定义接口方法时,不能使用synchronized关键字;
    • 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步;
    • 离开同步代码块后,所获得的锁会被自动释放;

    四,synchronized底层实现原理

            通 过下面的代码案例,观察一下synchronized的用法以及底层实现。

           案例:通过两个线程对变量sharedState进行10w次操作,观察每次操作后formerlatter之                      间的关系。

    1. public class ThreadSafeSample {
    2. public int sharedState;
    3. public synchronized void nonSafeAction() {
    4. while (sharedState < 100000) {
    5. int former = sharedState++;
    6. int latter = sharedState;
    7. if (former != latter - 1) {
    8. System.out.printf("数据观察结果: former is %d,latter is %d", former,latter);
    9. }
    10. }
    11. }
    12. public static void main(String[] args) throws InterruptedException {
    13. ThreadSafeSample sample = new ThreadSafeSample();
    14. Thread threadA = new Thread(){
    15. public void run(){
    16. sample.nonSafeAction();
    17. }
    18. };
    19. Thread threadB = new Thread(){
    20. public void run(){
    21. sample.nonSafeAction();
    22. }
    23. };
    24. threadA.start();
    25. threadB.start();
    26. threadA.join();
    27. threadB.join();
    28. System.out.println(sample.sharedState);
    29. }
    30. }

     然后我们利用Java反编译,可以看到:

    11: astore_1
    12: monitorenter
    13: aload_0
    14: dup
    15: getfield      #2                  // Field sharedState:I
    18: dup_x1

    56: monitorexit

     利用monitorenenter/monitorexit实现了同步的语义。

              所以,synchronized代码块是由一对monitorenter/monitorexit指令实现,synchronized是通过对象内部的叫做监视器(monitor)来实现的,线程通过执行monitorenter指令尝试获取monitor的所有权,当monitor被占用时就会处于锁定状态。

    监视器(monitor):

              在JVM中实现的规范中监视器的描述为:每个对象有一个监视器(monitor),线程通过执行monitorenter指令尝试获取monitor的所有权,当monitor被占用时就会处于锁定状态。

      获取monitor所有权的过程如下

    • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者,代表持有锁;
    • 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
    • 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权

    295d1ad7e0e7408a9cfb18db72e3ac79.png

     

     

     

  • 相关阅读:
    前端,HTTP协议,HTML介绍
    U-Boot 启动流程详解
    练习一:用python发邮件
    哈希切割+布隆过滤器
    C语言实现windows,linux双版本下的进度条小程序,快来试一试吧
    单元测试实战(四)MyBatis-Plus 的测试
    Java业务场景(一):实现屏蔽手机号码功能 && 用户手机号隐私功能 && 字符串截取 || 拼接
    陈宇(Aqua)-安全->云安全->多云安全
    awk提取nginx日志相应字段
    Android 12.0 修改系统默认字体的大小
  • 原文地址:https://blog.csdn.net/weixin_54535063/article/details/126768463