zoukankan      html  css  js  c++  java
  • Java中常用的锁分析总结

    Java中常用的锁分析总结

    1.    ReentrantLock、ReentrantReadWriteLock及Sychronized简介

    (a)  类继承结构


    ReentrantLock类继承结构:


    ReentrantReadWriteLick类继承结构:

    简述:通过类的继承结构可以看出ReentrantLock 和 ReentrantReadWriteLock是拥有者两个不同类继承结构的体系,两者并无关联。

    Ps:Sychronized是一个关键字

    (b) 几个相关概念

    什么是可重入锁:可重入锁的概念是自己可以再次获取自己的内部锁。举个例子,比如一条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的(如果不可重入的锁的话,此刻会造成死锁)。说的更高深一点可重入锁是一种递归无阻塞的同步机制。

    什么叫读写锁:读写锁拆成读锁和写锁来理解。读锁可以共享,多个线程可以同时拥有读锁,但是写锁却只能只有一个线程拥有,而且获取写锁的时候其他线程都已经释放了读锁,而且该线程获取写锁之后,其他线程不能再获取读锁。简单的说就是写锁是排他锁,读锁是共享锁。

    获取锁涉及到的两个概念即 公平和非公平:公平表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO顺序。而非公平就是一种获取锁的抢占机制,和公平相对就是先来不一定先得,这个方式可能造成某些线程饥饿(一直拿不到锁)。

    (c)  ReentrantLock,ReentrantReadWriteLock,Sychronized用法即作用

    ReentrantLock: 类ReentrantLock实现了Lock,它拥有与Sychronized相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断等候的一些特性。此外,它还提供了在与激烈争用情况下更佳的性能(说白了就是ReentrantLock和Sychronized差不多,线程间都是完全互斥的,一个时刻只能有一个线程获取到锁,执行被锁住的代码,但ReentrantLock相对于Sychronized提供了更加丰富的功能并且在线程调度上做了优化,JVM调度使用ReentrantLock的线程会更快)

    代码示例:ReentrantLockTest.java

    /**

     * ReentrantLock DEMO

     * @author jianying.wcj

     * @date 2013-5-20

     */

    public class ReetrantLockTest  {

           /**

            * 一个可重入锁成员变量

            */

           private ReentrantLocklock =new ReentrantLock();

           public static void main(String[] args) {

                  ReetrantLockTestdalt = new ReetrantLockTest();

               dalt.testLock();

           }

           public void testLock(){

                  for(int i = 0; i < 5; i++) {

                         Threadthread = new Thread(new Runnable(){

                                              @Override

                                              publicvoid run() {

                                                     sayHello();

                                              }

                                       },"thread"+i);

                         thread.start();

                  }

           }

           public void sayHello() {

                  /**

                   * 当一条线程不释放锁的时候,第二个线程走到这里的时候就阻塞掉了

                   */

                  try {

                  lock.lock();

                         System.out.println(Thread.currentThread().getName()+" locking ...");

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

                         System.out.println(Thread.currentThread().getName()+" unlocking ...");

                  }finally {

                      lock.unlock();

               }

        }

    }

       执行结果:


      

    简述:首先要操作ReentrantLock的加锁(lock)和解锁(unlock)必须是针对同一个ReentrantLock对象,要是new 两个ReetrantLock来分别完成对同一资源的加锁和解锁是没有意义的。比如LockA对象对 resource 加锁,让后LockB对象对Resource解锁,这个是不对的,没有意义的)。通过执行结果可以看出,当一个线程去lock资源的时候,必须是上一个线程对资源完成了unlock,这个和syncronized关键字启动的作用是一样的。 另外在使用时一个需要格外主意的点是 unlock方法的调用要放在finally代码块里,来保证锁一定会释放,否则可能造成某一个资源一直被锁死,排查问题比较困难。

    ReentrantReadWriteLock:类ReentrantReadWriteLock实现了ReadWirteLock接口。它和ReentrantLock是不同的两套实现,在类继承结构上并无关联。和ReentrantLock定义的互斥锁不同的是,ReentrantReadWriteLock定义了两把锁即读锁和写锁。读锁可以共享,即同一个资源可以让多个线程获取读锁。这个和ReentrantLock(或者sychronized)相比大大提高了读的性能。在需要对资源进行写入的时候在会加写锁达到互斥的目的。话不多说看DEMO:

    ReentrantReadWriteLock.java:

    public class ReadWriteLockTest {

           /**

            * 一个可重入读写锁

            */

           private ReentrantReadWriteLockreadWriteLock =new ReentrantReadWriteLock();

           /**

            * 读锁

            */

           private ReadLockreadLock =readWriteLock.readLock();

           /**

            * 写锁

            */

           private WriteLockwriteLock =readWriteLock.writeLock();

           /**

            * 共享资源

            */

           private StringshareData ="寂寞等待中...";

          

           public void write(String str) throws InterruptedException {

                 

            writeLock.lock();

                  System.err.println("ThreadName:"+Thread.currentThread().getName()+"locking...");

                  try {

                         shareData = str;

                         System.err.println("ThreadName:" + Thread.currentThread().getName()+"修改为"+str);

                         Thread.sleep(1);

                  }catch(InterruptedException e) {

                         e.printStackTrace();

                  }finally {

                         System.err.println("ThreadName:" + Thread.currentThread().getName()+"  unlock...");

                         writeLock.unlock();

                  }

           }

          

           public String read() {

                 

                  readLock.lock();

                  System.out.println("ThreadName:" + Thread.currentThread().getName()+"lock...");

                  try {

                         System.out.println("ThreadName:"+Thread.currentThread().getName()+"获取为:"+shareData);

                         Thread.sleep(1);

                  }catch(InterruptedException e) {

                         e.printStackTrace();

                  }finally {

                         System.out.println("ThreadName:" + Thread.currentThread().getName()+"unlock...");

                         readLock.unlock();

                  }

                  returnshareData;

           }

          

           public static void main(String[] args) {

                  final ReadWriteLockTest shareData =new ReadWriteLockTest();

                  /**

                   * 起50条读线程

                   */

                  for(int i = 0; i < 50; i++) {

                         new Thread(new Runnable() {

                                publicvoid run() {

                                              try {

                                                     Thread.sleep(1);

                                              }catch (InterruptedException e) {

                                                     e.printStackTrace();

                                              }

                                              shareData.read();

                                       }

                         },"get Thread-read"+i).start();

                  }

                 

                  for(int i = 0; i < 5; i++) {

                         new Thread(new Runnable() {

                                publicvoid run() {

                                       try {

                                              Thread.sleep(1);

                                       }catch (InterruptedException e1) {

                                              e1.printStackTrace();

                                       }

                                       try {

                                              shareData.write(new Random().nextLong()+"");

                                       }catch (InterruptedException e) {

                                              e.printStackTrace();

                                       }

                                }

                         },"wirte Thread-write"+i).start();

                  }

           }

    }

    运行结果:



     

     

    简述:Demo读锁和写锁都是ReentrantReadWriteLock类定义的内部公开类,要想让读锁和读锁或者读锁跟写锁产生共享或者互斥关系,必须要求读锁和写锁是有同一个ReentrantReadWriteLock产生的,否则是没有意义的。从运行结果中可以看出读锁之间的共享,写锁和写锁,写锁和读锁之间的互斥关系。

    Synchronized关键字:

    public class SychronizedTest implements Runnable{

     

        public void run() { 

              synchronized(this) { 

                   for (int i = 0; i < 5; i++) { 

                        System.out.println(Thread.currentThread().getName()+"synchronized loop " + i); 

                   } 

              }

        }

        public static void main(String[] args) { 

                SychronizedTest t1 = new SychronizedTest(); 

              Thread ta = new Thread(t1,"A"); 

              Thread tb = new Thread(t1,"B"); 

              ta.start(); 

              tb.start(); 

        }

    }

    运行结果:


    ·

     

    简述:从运行记过来看,被sychronized包围的代码是原子的。这个不多说,这个关键字大家应该都很熟悉。

    2.    ReentrantLock、ReentrantReadWriteLock及Sychronized实现原理(源码级别)

    (a)  锁机制的内部实现

    ReentrantLock内部锁机制实现相关类图:


    简述:ReentrantLock锁机制的实现是基于它的一个成员变量sync,这个Sync是AbstractQueuedSynchronized(AQS)的一个子类(ps:sync类是ReentrantLock自己定义的一个内部类)。另外在ReentrantLock内部还定义了另外两个类,分别是FairSync和NonFairSync,这两个类就是分别对应的锁公平分配和不公平分配的两个实现,它们都继承自Sync(类图已经清晰的描述出来了继承结构)。有关锁的分配和释放逻辑都是封装在了AQS里面的(AQS是AbstractQueuedSynchronized的简称,是JSR166规范中提出的一个基础的同步中心类或者说是同步框架,其在内部实现了大量的同步操作,而且用户还可以在此类的基础上自定义自己的同步类),可见Sync和AQS是锁机制实现的核心类(AQS详述见下文)。

    ReentrantLock当中的部分实例代码:

    1.     两个构造函数(可见默认使用的非公平锁的分配机制):


    2.     Lock方法的实现其实就是直接代理了Sync lock的实现:


    3.     TryLock方法也是一样的,都是代理自Sync


    4.     解锁方法


    Ps:说白了ReentrantLock就是基于Sync的,而Sync就是一种AQS,其中核心机制AQS都实现好了。

                   ReentrantReadWriteLock内部实现机制实现类图:


              ReentrantReadWriteLock的类图和ReentrantLock的类图感觉是一摸一样的,唯一的区别就是Sync、FairSync、NonSync是ReentrantReadWriteLock自己定义的。因为ReentrantReadWriteLock要实现读写锁机制,所以这里的Sync和ReentrantLock的Sync肯定不会相同。其他的和ReentrantLock都是一样的,核心的实现都是基于AQS的子类Sync(AQS分析见下文)

                  部分示例代码如下:

          1.构造函数(内部定义了ReadLock和WriteLock,默认也采用锁非公平分配的实现)


            2. WriteLock当中的Lock方法:


       Ps:上文简单的贴了两行代码主要为了说明一点,ReentrantLock和ReentrantReadWriteLock的实现是基于AQS的。下文再从源码角度分析一下具体实现。

           Synchronized关键字:

           简述:Synchronized实现的同步和上面提到的AQS的方式是不同的,AQS实现了一套自己的算法来实现共享资源的合理控制(具体算法实现,下文分析),而Synchronized实现的同步控制是基于java 内部的对象锁的。

           Java内部对象锁:JVM中每个对象和类实际上都与一把锁与之相关联,对于对象来说,监视的是这个对象变量,对于类来说,监视的是类变量。当虚拟机装载类时,会创建一个Class类的实例,锁住的实际上是这个类对应的Class累的实例。对象锁是可重入的,也就是说一个对象或者类上的锁是可以累加的。

           Ps:java中的同步是通过监视器模型来实现的,Java中的监视器实际上是一个代码块.

          

    Synchronized实现分析:这么说还是有点抽象,那么从代码角度来分析一下Synchronized是怎么实现的。

    (a)   先看看Synchronized代码快的方式:

    SynchronizedTest1.java:

    package test9;

    /**

     * @author jianying.wcj

     * @date 2013-5-22

     */

    public classSynchronizedTest1 {

     

        public void sayHello(){

           synchronized(this){

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

           }

        }

    }

    先用javac编译成.class 然后再用javap–verbose SynchronizedTest1 查看自己码的汇编码如下图所示:


    简述:红色标记出来的是两条JVM命令,用来标识进入同步代码块,和退出同步代码块,由此可见Synchronized已经上升到JVM指令的级别和AQS的实现还是有很大差别的。上面这个是Synchronized代码块的形式,Synchronized还有另一种使用方式就是同步方法。

    (b)  Synchronized同步方法的方式:

    SynchronizedTest2.java:

    package test9;

    /**

     * @author jianying.wcj

     * @date 2013-5-22

     */

    public class SychronizedTest{

     

      public synchronized void sayHello(){

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

      }

    }

    同样通过javap命令查看汇编码如下:


    简述:通过看这段汇编码,并没有发现JVM的同步块指令,可见同步方法和代码同步块采用的是不同的实现方式。同步方法的实现是JVM定义了方法的访问标志 ACC_SYNCHRONIZED 在方法表中,JVM后将同步方法前面设置这个标志,用于标识这个是一个同步方法。

    3.    Sync及AQS的核心实现(源码级别)

    AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源的设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

    那么首先看一下CLH队列锁的数据结构及实现算法。

    (a)CLH队列的数据结构(如图):


    简述:CLH队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配的。具体构建队列的算法是这样的:

    假设: 有共享资源S目前正被L3线程占用,此时有L1、L2线程分别对资源S进行lock操作以及获取锁后进行unlock操作。具体的流程如下:

    (1)由于目前资源S被占用,所以将线程L1包装成一个CLH队列的Node,将这个Node的前驱(prev)指向当前对列里的队尾,放入队尾这个操作采用了CAS原语(原子操作)。如果当前的队尾为NULL,那么就建一个虚拟的Header,然后将T1线程挂载到虚拟Header下。核心代码如下:


    Ps:  addWaiter就是放入队列的操作。

     

    Ps:采用CAS将节点加入到队尾,如果队尾为null进入enq操作。


    Ps:创建了一个虚拟的Header

    (2) L2线程请求资源S,那么它和L1线程一样将自己加入到队尾,L2的prev指向L1,L1.next指向L2(双向队列嘛)。

    (3) 当L3释放资源即unlock的时候,唤醒与L3关联的下一个节点,同时释放当前节点。关键代码:

                   

     (b)每个结点类的属性及方法信息:


    属性简述:CANCELLED:表示因为超时或者中断,结点被设置为取消状态,被取消的状态结点不应该去竞争锁。SIGNAL:表示这个结点的继任结点被阻塞了,因为等待某个条件而被阻塞。CONDITION:表示这个结点在队列中,因为等待某个条件而被阻塞。这几个是常量属性默认值为:


    这几个常量用来设置waitStatus属性。

    Thread属性表示关联到这个结点的线程。Prev和next就是关联前后结点的索引变量。NextWaiter 记录的是这个结点是独占式还是可共享的属性。

    4.    几种锁的性能比较及使用场景(应用级别)

    对于性能的对比这篇博客介绍的比较好:

    http://blog.csdn.net/lantian0802/article/details/8948696



  • 相关阅读:
    动画处理<并行和串行>
    flutter 动画 practice
    flutter AnimationBuilder
    flutter 动画
    Flutter Animation AnimatedBuilder
    Flutter 与 Android 的交互
    Flutter:教你用CustomPaint画一个自定义的CircleProgressBar
    Flutter裁剪图片
    Flutter 实现图片裁剪
    soundpool
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3097749.html
Copyright © 2011-2022 走看看