zoukankan      html  css  js  c++  java
  • Java并发包中Lock的实现原理

     转自:http://www.knowsky.com/889710.html

    http://blog.csdn.net/ghsau/article/details/7461369/ 很好可以看下

    实现Lock接口的基本思想

              需要实现锁的功能,两个必备元素,一个是表示(锁)状态的变量(我们假设0表示没有线程获取锁,1表示已有线程占有锁),另一个是队列,队列中的节点表示因未能获取锁而阻塞的线程。为了解决多核处理器下多线程缓存不一致的问题,表示状态的变量必须声明为voaltile类型,并且对表示状态的变量和队列的某些操作要保证原子性和可见性。原子性和可见性的操作主要通过Atomic包中的方法实现。

     

          线程获取锁的大致过程(这里没有考虑可重入和尝试获取锁过程被中断或超时的情况)

              1. 读取表示状态的变量

            2. 如果表示状态的变量的值为0,那么当前线程尝试将变量值设置为1(通过CAS操作完成),当多个线程同时将表示状态的变量值由0设置成1时,仅一个线程能成功,其

               它线程都会失败

                2.1 若成功,表示获取了锁,

                      2.1.1 如果该线程已位于在队列中,则将其出列(并将下一个节点则变成了队列的第一个节点)

                      2.1.2 如果该线程未入列,则不用对队列进行维护

                      然后当前线程从lock方法中返回,对共享资源进行访问。

                 2.2 若失败,则当前线程将自身放入等待(锁的)队列中并阻塞自身,此时线程一直被阻塞在lock方法中,没有从该方法中返回(被唤醒后仍然在lock方法中,并回                 到第1步重新开始)

            3. 如果表示状态的变量的值为1,那么将当前线程放入等待队列中,然后将自身阻塞(被唤醒后仍然在lock方法中,并回到第1步重新开始)

              注意: 唤醒并不表示线程能立刻运行,而是表示线程处于就绪状态,可以运行而已

          线程释放锁的大致过程

            1. 释放锁的线程将状态变量的值从1设置为0,并唤醒等待(锁)队列中的队首节点,释放锁的线程从就从unlock方法中返回,继续执行线程后面的代码

            2. 被唤醒的线程(队列中的队首节点)和可能和未进入队列并且准备获取的线程竞争获取锁,重复获取锁的过程

            注意:可能有多个线程同时竞争去获取锁,但是一次只能有一个线程去释放锁,队列中的节点都需要它的前一个节点将其唤醒,例如有队列A<-B-<C ,即由A释放锁时                     唤醒B,B释放锁时唤醒C

    公平锁和非公平锁

             锁可以分为公平锁和不公平锁,重入锁和非重入锁(关于重入锁的介绍会在ReentrantLock源代码分析中介绍),以上过程实际上是非公平锁的获取和释放过程。

    公平锁严格按照先来后到的顺去获取锁,而非公平锁允许插队获取锁。

              公平锁获取锁的过程上有些不同,在使用公平锁时,某线程想要获取锁,不仅需要判断当前表示状态的变量的值是否为0,还要判断队列里是否还有其他线程,若队列中还有线程则说明当前线程需要排队,进行入列操作,并将自身阻塞;若队列为空,才能尝试去获取锁。而对于非公平锁,当表示状态的变量的值是为0,就可以尝试获取锁,不必理会队列是否为空,这样就实现了插队的特点。通常来说非公平锁的吞吐率比公平锁要高,我们一般常用非公平锁。

               这里需要解释一点,什么情况下才会出现,表示锁的状态的变量的值是为0而且队列中仍有其它线程等待获取锁的情况。

               假设有三个线程A、B、C。A线程为正在运行的线程并持有锁,队列中有一个C线程,位于队首。现在A线程要释放锁,具体执行的过程操作可分为两步:

                1. 将表示锁状态的变量值由1变为0,

                2. C线程被唤醒,这里要明确两点:(1)C线程被唤醒并不代表C线程开始执行,C线程此时是处于就绪状态,要等待CPU的轮询(2)C线程目前还并未出列,C线程                   要进入运行状态,并且通过竞争获取到锁以后才会出列。

                如果C线程此时还没有进入运行态,同时未在队列中的B线程进行获取锁的操作,B就会发现虽然当前没有线程持有锁,但是队列不为空(C线程仍然位于队列中),要满足先来后到的特点(B在C之后执行获取锁的操作),B线程就不能去尝试获取锁,而是进行入列操作。

    实现Condition接口的基本思想

             Condition 本质是一个接口,它包含如下方法

    // 让线程进入等待通知状态,在未接受到通知之前,可通过中断结束等待状态,并抛出异常

    void await() throws InterruptedException;

    // 让线程进入等通知待状态,无法被中断

    void awaitUninterruptibly();

    //让线程进入等待通知状态,超时结束等待状态,并抛出异常

    long awaitNanos(long nanosTimeout) throws InterruptedException;

    boolean await(long time, TimeUnit unit) throws InterruptedException;

    boolean awaitUntil(Date deadline) throws InterruptedException;

    //将同一等待条件下的一个线程,从等待通知状态转换为等待锁状态

    void signal();

    //将同一等待条件下的所有个线程,从等待通知阻塞状态转换为等待锁阻塞状态

     void signalAll();

               一个Condition实例的内部实际上维护了一个队列,队列中的节点表示由于(某些条件不满足而)线程自身调用await方法阻塞的线程。Condition接口中有两个重要的方法,即 await方法和 signal方法。线程调用这个方法之前该线程必须已经获取了Condition实例所依附的锁。这样的原因有两个,1对于await方法,它内部会执行释放锁的操作,所以使用前必须获取锁2对于signal方法,是为了避免多个线程同时调用同一个Condition实例的singal方法时引起的(队列)出列竞争。下面是这两个方法的执行流程。

              await方法:

                                1. 入列到条件队列(这里不是等待锁的队列

                                2. 释放锁

                                 3. 阻塞自身线程

                                 ------------被唤醒后执行-------------

                                4. 尝试去获取锁(执行到这里时线程已不在条件队列中,而是位于等待(锁的)队列中,参见signal方法)

                                    4.1 成功,从await方法中返回,执行线程后面的代码

                                    4.2 失败,阻塞自己(等待前一个节点释放锁时将它唤醒)

             注意:await方法时自身线程调用的,线程在await方法中阻塞,并没有从await方法中返回,当唤醒后继续执行await方法中后面的代码。可以看出await方法释放了锁,                     尝试获得锁。

             signal方法:

                               1. 将条件队列的队首节点取出,放入等待锁队列的队尾

                               2. 唤醒该节点对应的线程

             注意:signal是由其它线程调用

    Lock和Condition的使用例程

               下面这个例子,就是利用lock和condition实现B线程先打印一句信息后,然后A线程打印两句信息(不能中断),交替十次后结束

    ublic class ConditionDemo {
        volatile int key = 0;
        Lock l = new ReentrantLock();
        Condition c = l.newCondition();
        
        public static  void main(String[] args){
            ConditionDemo demo = new ConditionDemo();
            new Thread(demo.new A()).start();
            new Thread(demo.new B()).start();
        }
        
        class A implements Runnable{
            @Override
            public void run() {
                int i = 10;
                while(i > 0){
                    l.lock();
                    try{
                        if(key == 1){
                            System.out.println("A is Running");
                            System.out.println("A is Running");
                            i--;
                            key = 0;
                            c.signal();
                        }else{
                            c.awaitUninterruptibly();                        
                        }
                        
                    }
                    finally{
                        l.unlock();
                    }
                }
            }
            
        }
        
        class B implements Runnable{
            @Override
            public void run() {
                int i = 10;
                while(i > 0){
                    l.lock();
                    try{
                        if(key == 0){
                            System.out.println("B is Running");
                            i--;
                            key = 1;
                            c.signal();
                        }else{
                            c.awaitUninterruptibly();                        
                        }
                        
                    }
                    finally{
                        l.unlock();
                    }
                }
            }    
        }
    }

    Lock与synchronized的区别

    1. Lock的加锁和解锁都是由java代码配合native方法(调用操作系统的相关方法)实现的,而synchronize的加锁和解锁的过程是由JVM管理的

    2. 当一个线程使用synchronize获取锁时,若锁被其他线程占用着,那么当前只能被阻塞,直到成功获取锁。而Lock则提供超时锁和可中断等更加灵活的方式,在未能获取锁的     条件下提供一种退出的机制。

    3. 一个锁内部可以有多个Condition实例,即有多路条件队列,而synchronize只有一路条件队列;同样Condition也提供灵活的阻塞方式,在未获得通知之前可以通过中断线程以    及设置等待时限等方式退出条件队列。

    4. synchronize对线程的同步仅提供独占模式,而Lock即可以提供独占模式,也可以提供共享模式

  • 相关阅读:
    Quartz.Net系列(二):介绍、简单使用、对比Windows计划任务
    Quartz.Net系列(一):Windows任务计划程序
    Linux下swap到底有没有必要使用
    Linux服务器有大量的TIME_WAIT状态
    HTTP请求头中的X-Forwarded-For介绍
    Keepalived实现服务高可用
    Gitlab常规操作
    Git 常用命令
    HTTP常见状态码
    《Docker从入门到跑路》之多阶段构建
  • 原文地址:https://www.cnblogs.com/guweiwei/p/7156440.html
Copyright © 2011-2022 走看看