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

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。

posted on 2022-03-03 22:04  Love&Share  阅读(157)  评论(0编辑  收藏  举报

导航