zoukankan      html  css  js  c++  java
  • 锁机制

    在java中的锁分为以下(其实就是按照锁的特性和设计来划分

    1、公平锁/非公平锁

    2、可重入锁

    3、独享锁/共享锁

    4、互斥锁/读写锁

    5、乐观锁/悲观锁

    6、分段锁

    7、偏向锁/轻量级锁/重量级锁

    8、自旋锁(java.util.concurrent包下的几乎都是利用锁)

    从底层角度看常见的锁也就两种:Synchronized和Lock接口以及ReadWriteLock接口(读写锁)

    从类关系看出Lock接口是jdk5后新添的来实现锁的功能,其实现类:ReentrantLock、WriteLock、ReadLock。

    其实还有一个接口ReadWriteLock,读写锁(读读共享、读写独享、写读独享、写写独享)。

    Lock接口与synchronized关键字本质上都是实现同步功能。

    区别:ReentrantLock:使用上需要显示的获取锁和释放锁,提高可操作性、可中断的获取获取锁以及可超时的获取锁,默认是                                          非公平的但可以实现公平锁,悲观,独享,互斥,可重入,重量级锁。

               ReentrantReadWriteLock:默认非公平但可实现公平的,悲观,写独享,读共享,读写,可重入,重量级锁。

               synchronized:关键字,隐式的获取锁和释放锁,不具备可中断、可超时,非公平、互斥、悲观、独享、可重入的重量级Lock的使用也很简单:

    Lock lock = new ReentrantLock();
    lock.lock();
    try{
     
    }finally{
        lock.unlock();
    }
    //注意:不要将lock方法写在try块中,因为如果在获取锁的时候发生异常,异常抛出的同时也会导致锁无故的释
    //放  否则会程序会报监视状态异常
    Exception in thread "线程一" java.lang.IllegalMonitorStateException
        at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
        at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
    //ReentrantLock必须要在finally中unlock(), 否则,如果在被加锁的代码中抛出了异常,那么这个锁将会永远无法释放. 
     
    //synchronized就没有这样的问题, 遇到异常退出时,会释放掉已经获得的锁。

    Lock接口提供的 ,synchronized关键字所不具备的特性

    以下测试代码,测试

    Lock lock = new ReentrantLock();
             final MMT m = new MMT(lock);
             Thread tt = new Thread(new Runnable() {
                 @Override
                 public void run() {
                     System.out.println("线程一 开始执行。。。");
                     try {
                        m.update("张三");
                    } catch (InterruptedException e) {
                               System.out.println(Thread.currentThread().getName()+"被中断(锁释放)。。。");
                    }
                     System.out.println("线程一 结束执行。。。");
                 }
             },"线程一");
             
             Thread tt2 = new Thread(new Runnable() {
                 @Override
                 public void run() {
                     
                     System.out.println("线程二 开始执行。。。");
                     try {
                        m.update("李四");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        System.out.println(Thread.currentThread().getName()+"被中断(锁释放)。。。");
                    }
                     System.out.println("线程二 结束执行。。。");
                 }
             },"线程二");
             
             tt.start();
             tt2.start();
             //中断线程
             tt.interrupt();
             try {
                tt.join();
                tt2.join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
             
            
        }
     
    class MMT {
        String name;
        Lock lock=null;
        public MMT(Lock lock) {
            this.lock=lock;
        }
       public void update(String name) throws InterruptedException{
    //       lock.lock();
    //       boolean tryLock = lock.tryLock();//尝试获取锁
           //中断只是在当前线程获取锁之前,或者当前线程获取锁的时候被阻塞
    //       lock.lockInterruptibly();
           lock.tryLock(3000, TimeUnit.SECONDS);
           try{
               setName(name);
               System.out.println(Thread.currentThread().getName()+" 变换后的姓名为"+name);
           }finally{
               lock.unlock();
           }
       }
       
       public void setName(String name) {
        this.name = name;
       }
       public String getName() {
        return name;
       }
    }

    可实现公平锁

        对于ReentrantLock而言,可实现公平锁 ,通过构造函数指定是否需要公平,默认是非公平,区别在与非公平随机性,并且高并发下吞吐量大,公平的话根据请求锁等待的时间长短,等待的长了优先,类似FIFO,吞吐量降低了。

    锁绑定多个条件

           指ReentrantLock对象可以同时绑定多个Condition条件对象,而在Synchroized中,锁对象的wait方法、notify方法、和notifyall方法可以实现一个隐含条件,如果需要多个,得额外的添加一个锁对象。在ReentrantLock中不需要,只需要创建多个条件对象即可(new Condition()),对应的await()、siganl()、signalAll()。

    synchronized的优势

    synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

    应用场景:

       在资源竞争不激烈的情况下,synchronized关键字的性能优与ReentrantLock,相反,ReentrantLock的性能保持常态,优于关键字。

    按照其性质划分:

    公平锁/非公平锁

           公平锁指多个线程按照申请锁的顺序来依次获取锁。非公平锁指多个线程获取锁的顺序并不是按照申请锁的顺序来获取,有可能后申请锁的线程比先申请锁的线程优先获取到锁,此极大的可能会造成线程饥饿现象,迟迟获取不到锁。由于ReentrantLock是通过AQS来实现线程调度,可以实现公平锁,,但是synchroized是非公平的,无法实现公平锁。

    /**
     * 公平锁与非公平锁测试
     */
    public class FairAndUnFairThreadT {
     
     
        public static void main(String[] args) throws InterruptedException {
            //默认非公平锁
            final Lock lock = new ReentrantLock(true);
            final MM m = new MM(lock);
            for (int i=1;i<=20 ;i++){
                String name = "线程"+i;
                Thread tt = new Thread(new Runnable() {
                    @Override
                    public void run() {
                       for(int i=0;i<2;i++){
                           m.testReentrant();
                       }
                    }
                },name);
                tt.start();
            }
     
        }
    }
    class MM {
        Lock lock = null;
        MM(Lock lock){
            this.lock = lock;
        }
     
        public void testReentrant(){
            lock.lock();
            try{
                Thread.sleep(1);
                System.out.println(Thread.currentThread().getName() );
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
     
        public synchronized  void testSync(){
            System.out.println(Thread.currentThread().getName());
        }
     
    }

    但是未必绝对就是按照顺序,可能因为CPU准备原因,可能个别会不是公平的。

    乐观锁与悲观锁

          不是指什么具体类型的锁,而是指在并发同步的角度。悲观锁认为对于共享资源的并发操作,一定是发生xi修改的,哪怕没有发生修改,也会认为是修改的,因此对于共享资源的操作,悲观锁采取加锁的方式,认为,不加锁的并发操作一定会出现问题。乐观锁认为对于共享资源的并发操作是不会发生修改的,在更新数据的时候,会采用尝试更新,不断重试的方式更新数据。乐观的认为,不加锁的并发操作共享资源是没问题的。从上面的描述看除,乐观锁不加锁的并发操作会带来性能上的提升,悲观锁的使用就是利用synchroized关键字或者lock接口的特性。乐观锁在java中的使用,是无锁编程常常采用的是CAS自旋锁,典型的例子就是并发原子类,通过CAS自旋(spinLock)来更新值。

    独享锁与共享锁

        独享锁是指该锁一次只能被一个线程所持有。共享锁是指可被多个线程所持有。在java中,对ReentrantLock对象以及synchroized关键字而言,是独享锁的。但是对于ReadWriteLock接口而言,其读是共享锁,其写操作是独享锁。读锁的共享锁是可保证并发读的效率,读写、写写、写读的过程中都是互斥的,独享的。独享锁与共享锁在Lock的实现中是通过 AQS(抽象队列同步器)来实现的。

    互斥锁与读写锁

          互斥锁与读写锁就是具体的实现,互斥锁在java 中的体现就是synchronized关键字以及Lock接口实现类ReentrantLock,读写锁在java中的具体实现就是ReentrantReadWriteLock。

    可重入锁

       又名递归锁,是指同一个线程在外层的方法获取到了锁,在进入内层方法会自动获取到锁。对于ReentrantLock和synchronized关键字都是可重入锁的。最大的好处就是能够避免一定程度的死锁。

    public sychrnozied void test() {
        //执行逻辑,调用另一个加锁的方法
        test2();
    }
     
    public sychronized void test2() {
        //执行业务逻辑
    }

    在上面代码中,sychronized关键字加在类方法上,执行test方法获取当前对象作为监视器的对象锁,然后又调用test2同步方法。

    一、如果锁是可重入的话,那么当前线程就在调用test2时并不需要再次获取当前锁对象,可以直接进入test2方法。

    二、如果锁是不具备可重入的话,那么该线程在调用test2前会等待当前对象锁的释放,实际上该对象锁已被当前线程所持有不可能再此获得。那么就会发生死锁。

    按照设计方案来分类(目的对锁的进一步优化)

    自旋锁与自适应自旋锁(或者说是自旋锁的变种TicketLock、MCSLock、CLHLock)

    底层采用CAS来保证原子性,自旋锁获取锁的时候不会阻塞,而是通过不断的while循环的方式尝试获取锁。优点:减少线程上下文切换的消耗,缺点是会消耗CPU。如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性能上的浪费。

    偏向锁、轻量级锁、重量级锁

    这三种锁是指锁的状态,并且是针对Synchronized,在java通过引入锁升级的机制来实现高校的synchronized。锁的状态是通过对象监视器在对象头中的字段来表明的。

    偏向锁:指一段同步代码一直被同一个线程s所访问,那么该线程会自动的获取锁。降低获取锁的代价。

    轻量级锁:当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

    重量级锁:当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没获取到锁就会进入阻塞,该锁膨胀为重量级锁。重量级会让其他申请线程阻塞,性能降低。 

    偏向所锁,轻量级锁都是乐观锁,重量级锁是悲观锁。一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。

    一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。
    轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

    分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

    当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。

    但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

  • 相关阅读:
    Java之JVM调优案例分析与实战(3)
    Java之JVM调优案例分析与实战(2)
    Java之JVM调优案例分析与实战(1)
    Creating a Fragment: constructor vs newInstance()
    Patterns-Observer
    Global Times 单词(日常收集)
    Unity3D入门工具介绍(一)
    指定安装应用程序移至SD卡(App2SD)
    Android源代码目录结构(转)
    技术路线的选择重要但不具有决定性(转)
  • 原文地址:https://www.cnblogs.com/sxw123/p/13807565.html
Copyright © 2011-2022 走看看