• 互斥锁(上):解决原子性问题


    1)回顾一下什么是原子性?

    • 一个或多个操作在CPU执行的过程中不被中断的特性,称为原子性。

    2)原子性问题的源头是什么?

    • 线程切换

    3)原子性问题到底该如何解决呢?

    • 既然原子性问题的产生源头是线程切换,而线程切换依赖CPU中断的,所以禁止CPU发生中断就能禁止线程切换

    4)为什么说单核的时候好解决原子性,而多核不好解决?

    • 人多了不好管理

    5)32 位 CPU 上执行 long 型变量的写操作是怎样的步骤?

    • 被分成了两次写操作(写高 32 位和写低 32 位)

    6)互斥怎么理解?

    • 同一时刻只有一个线程执行

    7)保证原子性的关键是什么?

    • 保证对共享变量的修改是互斥的

    8)互斥的最简单方案就是加锁,那么简易锁模型是怎样的?

    9)简易锁模型有哪些不足

    • 我们不能清晰的知道锁的是什么?我们保护的又是什么?

    10)从哪些方面去改进我们的简易锁模型?

    • 在现实世界里,锁和锁要保护的资源是有对应关系的,比如你用你家的锁保护你家的东西,我用我家的锁保护我家的东西。

    • 在并发编程世界里,锁和资源也应该有这个关系。

    11)改进后的锁模型应该是怎样的?

    12)改进后的锁有哪些注意要点?

    • 锁 LR 和受保护资源之间,我特地用一条线做了关联,这个关联关系非常重要。很多并发 Bug 的出现都是因为把它忽略了,然后就出现了类似锁自家门来保护他家资产的事情

    13)Java 语言提供的锁技术是什么?

    • synchronized

    14)synchronized可以锁哪些对象?

    • 方法

    • 代码块

    15)写一段代码看看?

     
     class X {
       // 修饰非静态方法
       synchronized void foo() {
         // 临界区
      }
       // 修饰静态方法
       synchronized static void bar() {
         // 临界区
      }
       // 修饰代码块
       Object obj = new Object()
       void baz() {
         synchronized(obj) {
           // 临界区
        }
      }
     }  

    16) 为什么代码中没有看见加锁---解锁这个顺序?,不符合上面的模型啊!

    • java悄悄滴帮我们加上了。

    17)java自动帮我们加锁解锁对我们有什么好处?

    • 加锁 lock() 和解锁 unlock() 一定是成对出现的,避免我们忘记解锁导致bug

    18)上面的代码中synchronized在代码块中锁的对象是obj。那它在修饰方法的时候锁的对象是什么?

    • 修饰静态方法:锁的是当前类的 Class 对象

       // 上面的例子相当于
       class X {
         // 修饰静态方法
         synchronized(X.class) static void bar() {
           // 临界区
        }
       }

       

    • 修饰非静态方法:锁的是当前实例对象 this

       
       class X {
         // 修饰非静态方法
         synchronized(this) void foo() {
           // 临界区
        }
       }

       

    19)怎样用synchronized 来解决 count+=1 问题?

     class SafeCalc {
       long value = 0L;
       long get() {
         return value;
      }
       synchronized void addOne() {
         value += 1;
      }
     }

    20)上面的代码真的解决了并发问题吗?

    • addOne() 方法,被 synchronized 修饰后。无论是单核 CPU 还是多核 CPU,只有一个线程能够执行 addOne() 方法,所以一定能保证原子操作

    20.1)虽然synchronized解决了addOne() 原子性问题,那是否有可见性问题呢?

    • 前一个线程在临界区修改的共享变量(该操作在解锁之前),对后续进入临界区(该操作在加锁之后)的线程是可见的。

    • 我们上面说的前后线程可见针对的对象是addOne() 方法。

    现在我要求的是get() 方法去读addOne() 方法,他两没啥关系,所以get()方法是看不见我们addOne()方法的变化的

    20.2)如何解决get()方法看不见addOne()方法的问题呢?

    • get() 方法也 synchronized 一下

       
       class SafeCalc {
         long value = 0L;
         synchronized long get() {
           return value;
        }
         synchronized void addOne() {
           value += 1;
        }
       }
    • 他两锁的对象都是this,所以根据管程中的几大规则,就保证了我们get能够看见addOne的变化。

    20.3)上面代码锁模型图是怎么样的?

    你去看球赛,一张票一个座位。座位就是受保护资源,门票就是锁,检票人员就是synchronized。

    21)锁和受保护资源的关系是什么?

    • 一把锁可以锁多个资源,一个资源只能有一把锁。

    • 一张包场票包下所有座位,一个座位只能卖一张票,否则打架。

    22)分析下面改动的代码是否存在并发问题?

     
     class SafeCalc {
       static long value = 0L;
       synchronized long get() {
         return value;
      }
       synchronized static void addOne() {
         value += 1;
      }
     }

    静态方法的锁是类,而普通方法的锁是this对象,这是两把不同的锁,所以存在并发问题。

    23)下面的代码用 synchronized 修饰代码块来尝试解决并发问题,你觉得这个使用方式正确吗?有哪些问题呢?能解决可见性和原子性问题吗?

     
     class SafeCalc {
       long value = 0L;
       long get() {
         synchronized (new Object()) {
           return value;
        }
      }
       void addOne() {
         synchronized (new Object()) {
           value += 1;
        }
      }
     }
    • 1)两个代码块锁对象是不同的对象。

    • 2)经过JVM逃逸分析的优化后,这个sync代码直接会被优化掉,所以在运行时该代码块是无锁的

    24)加锁的本质是什么?

    • 在锁对象的对象头中写入当前线程id。

  • 相关阅读:
    CDH 下线节点
    Linux下如何查看哪些进程占用的CPU内存资源最多
    CentOS7安装iptables防火墙
    Mysql 编译报错 g++: internal compiler error: Killed (program cc1plus) 解决办法
    Jenkins配置gitlab
    mysql连接卡死,很多线程sleep状态,导致CPU中mysqld占用率极高
    c++0.1-----基于对象知识大综合(非指针篇)
    c++0.0-----挖坑
    python0.16------构造函数/析构函数/self详解/重写/访问限制/对象属性和类属性/@property/运算符重载
    python0.15-----继承和多态
  • 原文地址:https://www.cnblogs.com/YXBLOGXYY/p/15962117.html
Copyright © 2020-2023  润新知