锁的使用_java

一:简述

  1、说明

锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(常见的 synchronized 和 ReentrantLock等 ) 。

  2、分类介绍

并发中,会有各种各样锁如公平锁,乐观锁等,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计。
    公平锁/非公平锁
    可重入锁
    独享锁/共享锁
    互斥锁/读写锁
    乐观锁/悲观锁
    分段锁
    偏向锁/轻量级锁/重量级锁
    自旋锁

公平锁/非公平锁
    公平锁是指多个线程按照申请锁的顺序来获取锁。
    非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。
    对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
    对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。
    
可重入锁
    可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。
    对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁。
    对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。
    
    下面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。
    synchronized void setA() throws Exception{
        Thread.sleep(1000);
        setB();
    }

    synchronized void setB() throws Exception{
        Thread.sleep(1000);
    }
    
独享锁/共享锁
    独享锁是指该锁一次只能被一个线程所持有。
    共享锁是指该锁可被多个线程所持有。

    对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。
    读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。
    对于Synchronized而言,当然是独享锁。    
    
互斥锁/读写锁
    上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。
    互斥锁在Java中的具体实现就是ReentrantLock,读写锁在Java中的具体实现就是ReadWriteLock。    
    
乐观锁/悲观锁
    乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
    悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
    乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。

    悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。
    悲观锁在Java中的使用,就是利用各种锁。乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。    
    
分段锁
        分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。
        我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,
    数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,
    然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才
    能统计。分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。    

偏向锁/轻量级锁/重量级锁
        这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
        偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
        轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
        重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程
    进入阻塞,性能降低。

自旋锁
    在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去,好处是减少线程上下文切换的消耗,相应快。缺点是线程不停增加时,性能下降,因为每个线程都需要执行,占用CPU时间。

二:Java中的锁

  1、Lock

Lock
  Lock是一个接口,在Lock中声明了五个方法。
    lock()
        lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
        
    tryLock()
        tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。
    
    tryLock(long time, TimeUnit unit)
        tryLock(long time, TimeUnit unit)方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
        
    lockInterruptibly()
        lockInterruptibly()方法比较特殊,这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()
    想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
        单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中
    断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。
        
    unLock()
        unLock()方法是用来释放锁的。
    
ReentrantLock
  ReentrantLock意思是“可重入锁”,是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。
    getHoldCount()
        查询当前线程保持此锁的次数。
    getOwner()
        返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。
    getQueuedThreads()
        返回一个 collection,它包含可能正等待获取此锁的线程。
    getQueueLength()
        返回正等待获取此锁的线程估计数。
    getWaitingThreads(Condition condition)
        返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。
    getWaitQueueLength(Condition condition)
        返回等待与此锁相关的给定条件的线程估计数。
    hasQueuedThread(Thread thread)
        查询给定线程是否正在等待获取此锁。
    hasQueuedThreads()
        查询是否有些线程正在等待获取此锁。
    hasWaiters(Condition condition)
        查询是否有些线程正在等待与此锁有关的给定条件。
    isFair()
        如果此锁的公平设置为 true,则返回 true。
    isHeldByCurrentThread()
        查询当前线程是否保持此锁。
    isLocked()
        查询此锁是否由任意线程保持。
    lock()
        获取锁。
    lockInterruptibly()
        如果当前线程未被中断,则获取锁。
    newCondition()
        返回用来与此 Lock 实例一起使用的 Condition 实例。
    toString()
        返回标识此锁及其锁定状态的字符串。
    tryLock()
        仅在调用时锁未被另一个线程保持的情况下,才获取该锁。
    tryLock(long timeout, TimeUnit unit)
        如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
    unlock()
        试图释放此锁。
        
ReentrantReadWriteLock
  ReentrantReadWriteLock读写锁,实现接口ReadWriteLock(),最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。    
    重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。 
    WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能. 
    ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,可以提高并发量。 
    不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。 
    WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常。

  2、Synchronized

   测试代码:https://www.cnblogs.com/chunxiaozhang/p/12666641.html

synchronized用法
    synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
    代码块            被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码;
    方法            被修饰的方法称为同步方法,其作用的范围是整个方法;
    静态的方法        其作用的范围是整个static修饰的静态方法;

synchronized类型
    synchronized(this)--对象锁,作用对象:this实例对象
        public void speak_this() {
            synchronized (this) {
                System.out.println ( Thread.currentThread ().getName () + ":开始。。。。。。。。。。" );
                try {
                    Thread.sleep ( 3000 );
                } catch (InterruptedException e) {
                    e.printStackTrace ( );
                }
                System.out.println ( Thread.currentThread ().getName () + ":结束。。。。。。。。。。" );
            }
        }
        
    synchronized(class)--类锁,作用对象:class代表的字节码对象
        public void speak_class() {
            synchronized (Peopele.class) {
                System.out.println ( Thread.currentThread ().getName () + ":开始。。。。。。。。。。" );
                try {
                    Thread.sleep ( 3000 );
                } catch (InterruptedException e) {
                    e.printStackTrace ( );
                }
                System.out.println ( Thread.currentThread ().getName () + ":结束。。。。。。。。。。" );
            }
        }
        
    synchronized(object)--任意锁,作用对象:object代表的对象
        public  void speak_obj() {
            synchronized (new Object ()) {
                System.out.println ( Thread.currentThread ().getName () + ":开始。。。。。。。。。。" );
                try {
                    Thread.sleep ( 3000 );
                } catch (InterruptedException e) {
                    e.printStackTrace ( );
                }
                System.out.println ( Thread.currentThread ().getName () + ":结束。。。。。。。。。。" );
            }
        }
    
    synchronized method--对象锁,作用对象:当前实例对象,即调用该方法的实例对象
        public synchronized void speak_void() {
            System.out.println ( Thread.currentThread ().getName () + ":开始。。。。。。。。。。" );
            try {
                Thread.sleep ( 3000 );
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }
            System.out.println ( Thread.currentThread ().getName () + ":结束。。。。。。。。。。" );
        }
    
    synchronized static method--类锁,作用对象:本类的字节码对象    
        public synchronized static void speak_static_void() {
            System.out.println ( Thread.currentThread ().getName () + ":开始。。。。。。。。。。" );
            try {
                Thread.sleep ( 3000 );
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }
            System.out.println ( Thread.currentThread ().getName () + ":结束。。。。。。。。。。" );
        }

synchronized总结:
  1、锁的互斥范围即为锁的作用范围,作用范围外不受影响。
  2、对象锁之间,类锁之间相互影响,类锁与对象锁之间无影响。即相同作用对象之间有影响
  3、synchronized(object)作用对象可以是任意,取决于object代表的对象,谨慎使用。
  4、synchronized(this)与synchronized method除作用范围不同,其他等价。
  5、synchronized(class)与synchroized static method除作用范围不同,其他等级。 synchronized的缺陷   举例一:   如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:   
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;    2)线程执行发生异常,此时JVM会让线程自动释放锁。   那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,影响程序执行效率。   举例二:   当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。   采用synchronized实现同步,会导致一个问题:当一个线程在进行读操作时,其他线程只能等待。因此需要一种机制使多个线程都只是进行读操作时,不会发生冲突,通过Lock就可以办到。

  3、Lock和synchronized比较

Lock和synchronized比较

  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  2)synchronized在发生异常时,会自动释放线程占有的锁;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  5)Lock可以提高多个线程进行读操作的效率。

  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时,此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
原文地址:https://www.cnblogs.com/chunxiaozhang/p/12664428.html