Java中的锁[原理、锁优化、CAS、AQS]

编程

1、为什么要用锁?

锁:是为了解决并发操作引起的脏读,导致的数据不一致性的问题

2、锁实现的基本原理

2.1、volatile

JAVA编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排它锁单独获取这个变量。Java语言提供了volatile,在某些情况下比锁要更加的方便。

volatile在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能够读到这个修改的值。


结论:如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为他不会引起线程的上下文的切换和调度。

2.2、synchronized

synchronized通过锁机制实现同步。

先来看下利用synchronized实现同步的基础:JAVA中的每个对象都可以作为锁。

具体表现为以下3种形式:

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的Class对象。
  • 对于同步方法块,锁是Synchronized括号里的配置对象。

    当一个线程试图访问同步代码块时,它首先必须获取锁,退出或抛出异常时必须释放锁。

2.2.1 synchronized实现原理

synchronized是基于Monitor来实现同步的。

Monitor从两个方面来支持线程之间的同步:

  • 互斥执行
  • 协作

  1. JAVA使用对象锁(使用Synchronized获取对象锁)保证工作在共享的数据集上的线程互斥执行。
  2. 使用 notify/notifyAll/wait 方法来协同不同线程之间的工作。
  3. Class和Object都关联了一个Monitor。


    Monitor 的工作原理:

    线程进入同步方法中,为了继续执行临界区的代码,线程必须获取Monitor锁。如果获取锁成功,将成为该监视者对象的拥有者。任意时刻内,监视者对象只属于一个活动线程(The Owner),拥有监视者对象的线程可以调用wait( )进入等待集合(Wait Set),同时释放监视锁,进入等待状态。

    其他线程调用notify( )/notifyAll( )接口唤醒等待集合中的线程,这些等待的线程需要重新获取监视锁后才能执行wait( )之后的代码。

    同步方法执行完毕了,线程退出临界区,并释放监视锁。

    参考文档:https://www.ibm.com/developerworks/cn/java/j-lo-synchronized

2.2.2 synchronized 具体实现

1、同步代码块采用monitorenter、monitorexit指令显式的实现。

2、同步方法则使用ACC_SYNCHRONIZED标记符隐式的实现。

通过实例来看看具体实现:

public class SynchronizedTest {

public synchronized void method1( ) {

System.out.println("hello world !");

}

public void method2( ) {

synchronized( this ) {

System.out.println("hello world ! ");

}

}

}

JAVA编译后的字节码如下:


monitorenter

每一个对象都有一个monitor,一个monitor只能被一个线程拥有。当一个线程执行到monitorenter指令时会尝试获取相应的对象的monitor,获取规则如下:

如果monitor的进入数为0,则该进程可以进入monitor,并将monitor进入数设置为1,该线程即为monitor的拥有者。

如果当前线程已经拥有monitor,只是重新进入,则进入monitor的进入数加1,所以synchronized关键字实现的锁是可重入的锁。

如果monitor已经被其他的线程拥有,则当前线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor。

monitorexit

只有拥有相应对象的monitor的线程才能执行monitorexit指令。每执行一次该指令monitor进入数减1,当进入数为0时当前线程释放monitor,此时其他阻塞县城将可以尝试获取该monitor。

以上是 Java中的锁[原理、锁优化、CAS、AQS] 的全部内容, 来源链接: utcz.com/z/512398.html

回到顶部