zoukankan      html  css  js  c++  java
  • Java中的锁

    关于锁:

    java的内置锁:

    每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。

    获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
    java的对象锁和类锁:

    java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,

    但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,
    类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,
    但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
    但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,
    它只是用来帮助我们理解锁定实例方法和静态方法的区别的

    Java中的synchronized意为同步,该关键字使用的就是java的内置锁,该关键字可以加在代码块上、方法上、静态方法、类。
    加在普通方法上时锁是this对象、也就是当一个线程访问该方法时会去获取this对象锁、会在执行完同步方法后释放该锁。
    若果在这期间有其他线程想要获取该锁时会进入线程的阻塞状态,等待该锁被释放。

    从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock。
    synchronized的缺陷:

    synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢?

    首先,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。其次,
    那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,
    但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
    因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),
    通过Lock就可以办到。还有,当有多个线程读写文件时,读操作和写操作会发生冲突现象,
    写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
    但是采用synchronized关键字来实现同步的话,就会导致一个问题:
    如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
    因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。


    总结一下

    也就是说Lock提供了比synchronized更多的功能。但是要注意以下几点:

    1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。
    Lock是一个类,通过这个类可以实现同步访问;
    2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,
    当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;
    而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

    java.util.concurrent.locks

    下面我们就来探讨一下java.util.concurrent.locks包中常用的类和接口。
    1.Lock:
    下面来逐个讲述Lock接口中每个方法的使用,lock()、tryLock()、tryLock(long time, TimeUnit unit)
    和lockInterruptibly()是用来获取锁的。unLock()方法是用来释放锁的。
    首先lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
    tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,
    如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。
    在拿不到锁时不会一直在那等待。tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,
    只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。
    如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
    lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,
    则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()
    想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法
    能够中断线程B的等待过程。
    由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中
    或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。
    注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为本身在前面的文章中讲过
    单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。
    因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。
    而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。
    2.ReentrantLock:
    ReentrantLock,意思是“可重入锁”,如果锁具备可重入性,则称作为可重入锁。
    像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:
    基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,
    比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,
    而是可以直接执行方法method2。
    ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。
    3.ReentrantReadWriteLock:

    ReentrantReadWriteLock实现了ReadWriteLock接口。

    ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。

    注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会
    一直等待释放读锁。如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,
    则申请的线程会一直等待释放写锁。
    4.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。所以说,在具体使用时要根据适当情况选择。

    锁的相关概念介绍

    1.可重入锁
    如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,
    可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。
    举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会
    调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。

    假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,
    假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经
    持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。
    而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。
    2.可中断锁
    可中断锁:顾名思义,就是可以相应中断的锁。在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
    如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,
    想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
    3.公平锁
    公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,
    等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
    非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
    在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
    而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
    在ReentrantLock中定义了2个静态内部类,一个是NotFairSync,一个是FairSync,分别用来实现非公平锁和公平锁。
    另外在ReentrantLock类中定义了很多方法,比如:
      isFair()        //判断锁是否是公平锁
      isLocked()    //判断锁是否被任何线程获取了
      isHeldByCurrentThread()   //判断锁是否被当前线程获取了
      hasQueuedThreads()   //判断是否有线程在等待该锁
      在ReentrantReadWriteLock中也有类似的方法,同样也可以设置为公平锁和非公平锁。不过要记住,
    ReentrantReadWriteLock并未实现Lock接口,它实现的是ReadWriteLock接口。
    4.读写锁
      读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。
      正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
      ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。
      可以通过readLock()获取读锁,通过writeLock()获取写锁。
  • 相关阅读:
    hdu 3666 差分约束系统
    hdu 1198农田灌溉
    常微分方程(阿諾爾德) Page 45 相空間,相流,運動,相曲線 註記
    高等微積分(高木貞治) 1.4節 例2
    常微分方程(阿諾爾德) Page 45 相空間,相流,運動,相曲線 註記
    解析函數論 Page 29 命題(2) 函數模的有界性
    高等微積分(高木貞治) 1.4節 例2
    解析函數論 Page 29 命題(1) 有界閉集上的一致連續性
    解析函數論 Page 29 命題(3) 模的下界的可達性
    解析函數論 Page 29 命題(2) 函數模的有界性
  • 原文地址:https://www.cnblogs.com/dingzuoheng/p/12805074.html
Copyright © 2011-2022 走看看