并发编程-互斥锁:解决原子性问题

背景

我们把多个操作在cpu执行中不被中断的特性叫原子性。而cpu的切换却是发生在cpu指令级别的,所以线程的切换造成了原子性问题。那么如果我们禁止cpu切换是否可行呢?答案是不行,因为在多核的情况下,如果有两个cpu线程同一时刻操作同一个变量,即使不发生线程切换,每个cpu各自执行也会发生错误(如执行count++操作,每个cpu执行的结果可能都是1)。这个问题的实质就是有多个线程在同时执行操作破坏了原子性,而如果我们能够保证同一时刻只有一个线程执行,也就是串行执行,那么即可保证原子性。因此解决方案就是互斥锁

java中的锁:synchronized

  • java提供的synchronized关键字,就是一种锁实现。可以用来修饰方法,也可以用来修饰代码块。
  • 编译器会在编译时,对synchronized修饰的方法或者代码块自动添加lock和unlock操作。
  • 注意:当修饰静态方法时,锁定的是当前的Class对象,当修饰非静态方法时,锁定的是当前实例对象this
  • 可见性:根据HB原则,对一个锁的解锁操作HB于后续对这个锁的加锁操作,再结合HB的传递性原则,能够得到前一个线程对临界区共享变量的修改(该操作再在锁之前),对后续进入临界区(该操作再加锁之后)的线程是可见的

synchronized的实现原理

synchronized代码块是由一对monitorenter/monitorexit指令实现的,而synchronized方法是通过ACC_SYNCHRONIZED标识符来实现的,无论是monitorenter/exit指令还是ACC_SYNCHRONIZED都是通过获取Monitor的所有权实现。

Monitor实现原理流程如下:

  1. 线程执行monitorenter指令,如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。如果其他线程已经占用了monitor,该线程进入blocked状态,并进入到entryList。如果获得锁的线程被wait方法阻塞,则转移到waitSet集合,如果在某个时候被notify或者notifyAll唤醒,则再次进入到entryList,重新等待去获取锁
  2. 执行monitorexit时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。这时entrylist的线程就可以尝试去获取moniter的所有权了

在Java 6之前,Monitor的实现是依靠操作系统内部的互斥锁,因为需要用户态与内核态的切换,所以同步操作是一个重量级的操作

现代的JDK中,jvm提供了三种不同的monitor实现,也就是常说的偏斜锁,轻量级锁,重量级锁。所谓锁的升级、降级、就是jvm优化synchronized机制,当jvm检测到不同的状态,就切换到适合的锁实现

猜你喜欢

转载自www.cnblogs.com/hello---word/p/10995609.html