精华内容
下载资源
问答
  • Java有哪些锁
    千次阅读
    2021-02-08 23:12:49

    学习目标:

    Java中有哪些锁


    学习产出:

    1、乐观锁 & 悲观锁
    两种锁只是一种概念

    乐观锁:乐观锁认为一个线程去拿数据的时候不会有其他线程对数据进行更改,所以不会上锁。

    实现方式:CAS机制、版本号机制

    悲观锁:悲观锁认为一个线程去拿数据时一定会有其他线程对数据进行更改。所以一个线程在拿数据的时候都会顺便加锁,这样别的线程此时想拿这个数据就会阻塞。比如Java里面的synchronized关键字的实现就是悲观锁。实现方式:就是加锁。

    2、独享锁 & 共享锁
    两种锁只是一种概念

    独享锁:该锁一次只能被一个线程所持有

    共享锁:该锁可以被多个线程所持有

    举例:

    synchronized是独享锁;

    可重入锁ReentrantLock是独享锁;

    读写锁ReentrantReadWriteLock中的读锁ReadLock是共享锁,写锁WriteLock是独享锁。

    独享锁与共享锁通过AQS(AbstractQueuedSynchronizer)来实现的,通过实现不同的方法,来实现独享或者共享。

    3、互斥锁 & 读写锁
    上面讲的独享锁/共享锁就是一种概念,互斥锁/读写锁是具体的实现。

    互斥锁的具体实现就是synchronized、ReentrantLock。ReentrantLock是JDK1.5的新特性,采用ReentrantLock可以完全替代替换synchronized传统的锁机制,更加灵活。

    读写锁的具体实现就是读写锁ReadWriteLock。

    4、可重入锁
    定义:对于同一个线程在外层方法获取锁的时候,在进入内层方法时也会自动获取锁。

    优点:避免死锁

    举例:ReentrantLock、synchronized

    5、公平锁 & 非公平锁
    公平锁:多个线程相互竞争时要排队,多个线程按照申请锁的顺序来获取锁。

    非公平锁:多个线程相互竞争时,先尝试插队,插队失败再排队,比如:synchronized、ReentrantLock

    6、分段锁
    分段锁并不是具体的一种锁,只是一种锁的设计。

    分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。CurrentHashMap底层就用了分段锁,使用Segment,就可以进行并发使用了,而HashMap确实非线程安全的,就差在了分段锁上。

    7、偏向锁 & 轻量级锁 & 重量级锁
    JDK 1.6 为了减少获得锁和释放锁所带来的性能消耗,在JDK 1.6里引入了4种锁的状态:无锁、偏向锁、轻量级锁和重量级锁,它会随着多线程的竞争情况逐渐升级,但不能降级。

    研究发现大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了不让这个线程每次获得锁都需要CAS操作的性能消耗,就引入了偏向锁。当一个线程访问对象并获取锁时,会在对象头里存储锁偏向的这个线程的ID,以后该线程再访问该对象时只需判断对象头的Mark Word里是否有这个线程的ID,如果有就不需要进行CAS操作,这就是偏向锁。当线程竞争更激烈时,偏向锁就会升级为轻量级锁,轻量级锁认为虽然竞争是存在的,但是理想情况下竞争的程度很低,通过自旋方式等待一会儿上一个线程就会释放锁,但是当自旋超过了一定次数,或者一个线程持有锁,一个线程在自旋,又来了第三个线程访问时(反正就是竞争继续加大了),轻量级锁就会膨胀为重量级锁,重量级锁就是Synchronized,重量级锁会使除了此时拥有锁的线程以外的线程都阻塞。

    更多相关内容
  • java有哪些锁?java锁种类盘点

    千次阅读 2021-03-09 17:09:38
    学习java一定要全方面的了解java的知识,这样才能更好的运用到工作中去,比如java锁的知识点,那么接下来,我们就来给大家讲解一下java锁种类。大家可以参考以下文章。1.公平锁/非公平锁公平锁是指多个线程按照申请...

    学习java一定要全方面的了解java的知识,这样才能更好的运用到工作中去,比如java锁的知识点,那么接下来,我们就来给大家讲解一下java锁种类。大家可以参考以下文章。

    1.公平锁/非公平锁

    公平锁是指多个线程按照申请锁的顺序来获取锁。

    非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

    2.可重入锁

    可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。

    对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁。

    对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。

    3.独享锁/共享锁

    独享锁是指该锁一次只能被一个线程所持有。

    共享锁是指该锁可被多个线程所持有。

    对于Java

    ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。

    读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。

    独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

    对于Synchronized而言,当然是独享锁。

    4.互斥锁/读写锁

    上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。

    互斥锁在Java中的具体实现就是ReentrantLock

    读写锁在Java中的具体实现就是ReadWriteLock

    5.乐观锁/悲观锁

    乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。

    悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。

    乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。

    6.分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

    我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

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

    但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

    分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

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

    这三种锁是指锁的状态,并且是针对Synchronized。在Java

    5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。

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

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

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

    8.自旋锁

    在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

    Java锁就是以上这些了,虽说看起来知识点比较多,但是也不难,都是一些定义的知识,不过再怎么样,大家也要学会运用到实例中去,这样才是真正的了解了最后大家如果想要了解更多java初识知识,敬请关注奇Q工具网。

    推荐阅读:

    展开全文
  • [Java多线程 五]---JAVA锁有哪些种类

    万次阅读 多人点赞 2017-09-02 12:09:25
    上一篇既然提到了,这一篇来详细介绍JAVA中的,也为之后JUC下的做一个铺垫 其实如果按照名称来说,大概以下名词: 自旋 ,自旋的其他种类,阻塞,可重入 ,读写 ,互斥 ,悲观 ,乐观 ...

    转载自:
    http://blog.csdn.net/sinat_33087001/article/details/73607625

    上一篇既然提到了锁,这一篇来详细介绍JAVA中的锁,也为之后JUC下的锁做一个铺垫
    其实如果按照名称来说,锁大概有以下名词:
    自旋锁 ,自旋锁的其他种类,阻塞锁,可重入锁 ,读写锁互斥锁悲观锁乐观锁公平锁偏向锁, 对象锁,线程锁,锁粗化锁消除轻量级锁重量级锁, 信号量,独享锁共享锁分段锁
    我们所说的锁的分类其实应该按照锁的特性和设计来划分

    概述

    其实从并发的角度来讲,按照线程安全的三种策略看,主要内容都集中在互斥同步里,我们所讨论的锁也集中在这个部分。这个部分的锁都是悲观锁,第二个部分是非阻塞同步,这个部分也就一种通过CAS进行原子类操作,这个部分可以看成乐观锁,其实也就是不加锁。第三个部分是无同步方案,包括可重入代码和线程本地存储。详情可见我的上一篇博客.

    http://blog.csdn.net/sinat_33087001/article/details/77644441

    我们这里主要讨论的就是互斥同步这一部分。

    常见的锁

    Synchronized和Lock

    其实我们真正用到的锁也就那么两三种,只不过依据设计方案和性质对其进行了大量的划分。

    以下一个锁是原生语义上的实现

    • Synchronized,它就是一个:非公平,悲观,独享,互斥,可重入的重量级锁

    以下两个锁都在JUC包下,是API层面上的实现

    • ReentrantLock,它是一个:默认非公平但可实现公平的,悲观,独享,互斥,可重入,重量级锁。
    • ReentrantReadWriteLocK,它是一个,默认非公平但可实现公平的,悲观,写独享,读共享,读写,可重入,重量级锁。

    ReentrantLock与synchronized 的区别

    本段内容引自http://houlinyan.iteye.com/blog/1112535

    ReentrantLock的高级操作

    中断等待

    ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候。

    线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定

    • 如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
    • 如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情

    ReentrantLock获取锁定有三种方式

    • lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁

    • tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false

    • tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;

    • lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到获取锁定,或者当前线程被别的线程中断

    可实现公平锁

    对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

    锁绑定多个条件

    锁绑定多个条件是指一个ReentrantLock对象可以同时绑定多个Condition对象,而在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含的条件,如果要和多于一个的条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无须这样做,只需要多次调用newCondition()方法即可。

    synchronized的优势

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

    应用场景

    在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;

    按照其性质分类

    公平锁/非公平锁

    公平锁是指多个线程按照申请锁的顺序来获取锁。非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

    乐观锁/悲观锁

    乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。悲观锁在Java中的使用,就是利用各种锁乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。

    独享锁/共享锁

    独享锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReentrantReadWriteLock,其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。对于Synchronized而言,当然是独享锁。

    互斥锁/读写锁

    上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。互斥锁在Java中的具体实现就是ReentrantLock读写锁在Java中的具体实现就是ReentrantReadWriteLock

    可重入锁

    可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Reentrant Lock重新进入锁。对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。

    public sychrnozied void test() {
        xxxxxx;
        test2();
    }
    
    public sychronized void test2() {
        yyyyy;
    }

    在上面代码段中,执行 test 方法需要获得当前对象作为监视器的对象锁,但方法中又调用了 test2 的同步方法。

    • 如果锁是具有可重入性的话,那么该线程在调用 test2 时并不需要再次获得当前对象的锁,可以直接进入 test2 方法进行操作。
    • 如果锁是不具有可重入性的话,那么该线程在调用 test2 前会等待当前对象锁的释放,实际上该对象锁已被当前线程所持有,不可能再次获得。

    如果锁是不具有可重入性特点的话,那么线程在调用同步方法、含有锁的方法时就会产生死锁。

    按照设计方案来分类

    锁优化里涉及到的,都在上一篇博客里提到了,大概有以下这么几种

    自旋锁/自适应自旋锁

    在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。更多具体的细节见上一篇博客里对自旋锁的描述

    http://blog.csdn.net/sinat_33087001/article/details/77644441#t27

    锁粗化/锁消除

    锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,那就可以把它们当做栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行。

    如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部

    锁粗化和消除其实设计原理都差不多,都是为了减少没必要的加锁更多详情见以下链接的上一篇博文介绍

    http://blog.csdn.net/sinat_33087001/article/details/77644441#t27

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

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

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

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

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

    http://blog.csdn.net/sinat_33087001/article/details/77644441#t27

    分段锁

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

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

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

    展开全文
  • 上面是很多的名词,这些分类并不是全是指的状态,的指的特性,的指的设计,下面总结的内容是对每个的名词进行一定的解释。 公平 / 非公平 公平 公平是指多个线程按照申请的顺序来获取。 ...

    点击上方 "程序员小乐"关注, 星标或置顶一起成长

    每天凌晨00点00分, 第一时间与你相约

    每日英文

    I hate hearing something that absolutely kills you inside and having to act like you don't care.

    我讨厌有时候明明听到了一些让自己心痛到死的话,还得装作若无其事。

    每日掏心

    这世界上根本不存在’不会做’这回事,当你失去了所有的依靠的时候,自然就什么都会了。

    来自:搜云库技术团队 | 责编:乐乐

    链接:segmentfault.com/a/1190000017766364

    程序员小乐(ID:study_tech)第 742 次推文   图片来自 Pexels

    往日回顾:美击杀伊朗二号人物,美伊“网络战”一触即发!

       正文   

    在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类。介绍的内容如下:

    1.公平锁 / 非公平锁

    2.可重入锁 / 不可重入锁

    3.独享锁 / 共享锁

    4.互斥锁 / 读写锁

    5.乐观锁 / 悲观锁

    6.分段锁

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

    8.自旋锁

    上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释。

    公平锁 / 非公平锁

    公平锁

    公平锁是指多个线程按照申请锁的顺序来获取锁。

    非公平锁

    非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

    对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
    对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

    可重入锁 / 不可重入锁

    可重入锁

    广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLocksynchronized都是可重入锁

    synchronized void setA() throws Exception{
       Thread.sleep(1000);
       setB();
    }
    synchronized void setB() throws Exception{
       Thread.sleep(1000);
    }

    上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。

    不可重入锁

    不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。看到一个经典的讲解,使用自旋锁来模拟一个不可重入锁,代码如下

    import java.util.concurrent.atomic.AtomicReference;
    
    public class UnreentrantLock {
    
       private AtomicReference<Thread> owner = new AtomicReference<Thread>();
    
       public void lock() {
           Thread current = Thread.currentThread();
           //这句是很经典的“自旋”语法,AtomicInteger中也有
           for (;;) {
               if (!owner.compareAndSet(null, current)) {
                   return;
               }
           }
       }
    
       public void unlock() {
           Thread current = Thread.currentThread();
           owner.compareAndSet(current, null);
       }
    }
    

    代码也比较简单,使用原子引用来存放线程,同一线程两次调用lock()方法,如果不执行unlock()释放锁的话,第二次调用自旋的时候就会产生死锁,这个锁就不是可重入的,而实际上同一个线程不必每次都去释放锁再来获取锁,这样的调度切换是很耗资源的。

    把它变成一个可重入锁

    import java.util.concurrent.atomic.AtomicReference;
    
    public class UnreentrantLock {
    
       private AtomicReference<Thread> owner = new AtomicReference<Thread>();
       private int state = 0;
    
       public void lock() {
           Thread current = Thread.currentThread();
           if (current == owner.get()) {
               state++;
               return;
           }
           //这句是很经典的“自旋”式语法,AtomicInteger中也有
           for (;;) {
               if (!owner.compareAndSet(null, current)) {
                   return;
               }
           }
       }
    
       public void unlock() {
           Thread current = Thread.currentThread();
           if (current == owner.get()) {
               if (state != 0) {
                   state--;
               } else {
                   owner.compareAndSet(current, null);
               }
           }
       }
    }
    

    在执行每次操作之前,判断当前锁持有者是否是当前对象,采用state计数,不用每次去释放锁。

    ReentrantLock中可重入锁实现

    这里看非公平锁的锁获取方法:

    final boolean nonfairTryAcquire(int acquires) {
       final Thread current = Thread.currentThread();
       int c = getState();
       if (c == 0) {
           if (compareAndSetState(0, acquires)) {
               setExclusiveOwnerThread(current);
               return true;
           }
       }
       //就是这里
       else if (current == getExclusiveOwnerThread()) {
           int nextc = c + acquires;
           if (nextc < 0) // overflow
               throw new Error("Maximum lock count exceeded");
           setState(nextc);
           return true;
       }
       return false;
    }

    在AQS中维护了一个private volatile int state来计数重入次数,避免了频繁的持有释放操作,这样既提升了效率,又避免了死锁。

    独享锁 / 共享锁

    独享锁和共享锁在你去读C.U.T包下的ReeReentrantLock和ReentrantReadWriteLock你就会发现,它俩一个是独享一个是共享锁。

    独享锁:该锁每一次只能被一个线程所持有。

    共享锁:该锁可被多个线程共有,典型的就是ReentrantReadWriteLock里的读锁,它的读锁是可以被共享的,但是它的写锁确每次只能被独占。

    另外读锁的共享可保证并发读是非常高效的,但是读写和写写,写读都是互斥的。

    独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。
    对于Synchronized而言,当然是独享锁。

    互斥锁 / 读写锁

    互斥锁

    在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。

    如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。在这种方式下,只有一个线程能够访问被互斥锁保护的资源

    读写锁

    读写锁既是互斥锁,又是共享锁,read模式是共享,write是互斥(排它锁)的。

    读写锁有三种状态:读加锁状态、写加锁状态和不加锁状态

    读写锁在Java中的具体实现就是ReadWriteLock

    一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
    只有一个线程可以占有写状态的锁,但可以有多个线程同时占有读状态锁,这也是它可以实现高并发的原因。当其处于写状态锁下,任何想要尝试获得锁的线程都会被阻塞,直到写状态锁被释放;如果是处于读状态锁下,允许其它线程获得它的读状态锁,但是不允许获得它的写状态锁,直到所有线程的读状态锁被释放;为了避免想要尝试写操作的线程一直得不到写状态锁,当读写锁感知到有线程想要获得写状态锁时,便会阻塞其后所有想要获得读状态锁的线程。所以读写锁非常适合资源的读操作远多于写操作的情况。

    乐观锁 / 悲观锁

    悲观锁

    总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。JavasynchronizedReentrantLock等独占锁就是悲观锁思想的实现。

    乐观锁

    总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Javajava.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的

    分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

    并发容器类的加锁机制是基于粒度更小的分段锁,分段锁也是提升多并发程序性能的重要手段之一。

    在并发程序中,串行操作是会降低可伸缩性,并且上下文切换也会减低性能。在锁上发生竞争时将通水导致这两种问题,使用独占锁时保护受限资源的时候,基本上是采用串行方式—-每次只能有一个线程能访问它。所以对于可伸缩性来说最大的威胁就是独占锁。

    我们一般有三种方式降低锁的竞争程度: 

    1、减少锁的持有时间 
    2、降低锁的请求频率 
    3、使用带有协调机制的独占锁,这些机制允许更高的并发性。

    在某些情况下我们可以将锁分解技术进一步扩展为一组独立对象上的锁进行分解,这成为分段锁。

    其实说的简单一点就是

    容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

    比如:在ConcurrentHashMap中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(N mod 16)个锁来保护。假设使用合理的散列算法使关键字能够均匀的分部,那么这大约能使对锁的请求减少到越来的1/16。也正是这项技术使得ConcurrentHashMap支持多达16个并发的写入线程。

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

    锁的状态

    1.无锁状态

    2.偏向锁状态

    3.轻量级锁状态

    4.重量级锁状态

    锁的状态是通过对象监视器在对象头中的字段来表明的。
    四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。
    这四种状态都不是Java语言中的锁,而是Jvm为了提高锁的获取与释放效率而做的优化(使用synchronized时)。

    偏向锁

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

    轻量级

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

    重量级锁

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


    自旋锁

    我们知道CAS算法是乐观锁的一种实现方式,CAS算法中又涉及到自旋锁,所以这里给大家讲一下什么是自旋锁。

    简单回顾一下CAS算法

    CAS是英文单词Compare and Swap(比较并交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数

    1.需要读写的内存值 V

    2.进行比较的值 A

    3.拟写入的新值 B

    更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B,否则不会执行任何操作。一般情况下是一个自旋操作,即不断的重试。

    什么是自旋锁?

    自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环

    它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。

    Java如何实现自旋锁?

    下面是个简单的例子:

    public class SpinLock {
       private AtomicReference<Thread> cas = new AtomicReference<Thread>();
       public void lock() {
           Thread current = Thread.currentThread();
           // 利用CAS
           while (!cas.compareAndSet(null, current)) {
               // DO nothing
           }
       }
       public void unlock() {
           Thread current = Thread.currentThread();
           cas.compareAndSet(current, null);
       }
    }

    lock()方法利用的CAS,当第一个线程A获取锁的时候,能够成功获取到,不会进入while循环,如果此时线程A没有释放锁,另一个线程B又来获取锁,此时由于不满足CAS,所以就会进入while循环,不断判断是否满足CAS,直到A线程调用unlock方法释放了该锁。

    自旋锁存在的问题

    1、如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。
    2、上面Java实现的自旋锁不是公平的,即无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题。

    自旋锁的优点

    1、自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快
    2、非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。(线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)

    可重入的自旋锁和不可重入的自旋锁

    文章开始的时候的那段代码,仔细分析一下就可以看出,它是不支持重入的,即当一个线程第一次已经获取到了该锁,在锁释放之前又一次重新获取该锁,第二次就不能成功获取到。由于不满足CAS,所以第二次获取会进入while循环等待,而如果是可重入锁,第二次也是应该能够成功获取到的。

    而且,即使第二次能够成功获取,那么当第一次释放锁的时候,第二次获取到的锁也会被释放,而这是不合理的。

    为了实现可重入锁,我们需要引入一个计数器,用来记录获取锁的线程数。

    public class ReentrantSpinLock {
       private AtomicReference<Thread> cas = new AtomicReference<Thread>();
       private int count;
       public void lock() {
           Thread current = Thread.currentThread();
           if (current == cas.get()) { // 如果当前线程已经获取到了锁,线程数增加一,然后返回
               count++;
               return;
           }
           // 如果没获取到锁,则通过CAS自旋
           while (!cas.compareAndSet(null, current)) {
               // DO nothing
           }
       }
       public void unlock() {
           Thread cur = Thread.currentThread();
           if (cur == cas.get()) {
               if (count > 0) {// 如果大于0,表示当前线程多次获取了该锁,释放锁通过count减一来模拟
                   count--;
               } else {// 如果count==0,可以将锁释放,这样就能保证获取锁的次数与释放锁的次数是一致的了。
                   cas.compareAndSet(cur, null);
               }
           }
       }
    }

    自旋锁与互斥锁

    1.自旋锁与互斥锁都是为了实现保护资源共享的机制。

    2.无论是自旋锁还是互斥锁,在任意时刻,都最多只能有一个保持者。

    3获取互斥锁的线程,如果锁已经被占用,则该线程将进入睡眠状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁释放。

    自旋锁总结

    1.自旋锁:线程获取锁的时候,如果锁被其他线程持有,则当前线程将循环等待,直到获取到锁。

    2.自旋锁等待期间,线程的状态不会改变,线程一直是用户态并且是活动的(active)。

    3.自旋锁如果持有锁的时间太长,则会导致其它等待获取锁的线程耗尽CPU。

    4.自旋锁本身无法保证公平性,同时也无法保证可重入性。

    5.基于自旋锁,可以实现具备公平性和可重入性质的锁。

    欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。

    欢迎各位读者加入程序员小乐技术群,在公众号后台回复“加群”或者“学习”即可。

    猜你还想看

    阿里、腾讯、百度、华为、京东最新面试题汇集

    分析 Spring 框架中的装配模式

    Java 会走向晦暗吗?Kotlin 会取而代之吗

    10 分钟看懂分布式事务

    关注「程序员小乐」,收看更多精彩内容
    

    嘿,你在看吗

    展开全文
  • Java锁有哪些种类,以及区别

    千次阅读 2021-03-05 18:56:45
    展开全部一、公平/非公平公平是指多个线程e69da5e887aa62616964757a686964616f31333363393061按照申请的顺序来获取。...对于Java ReentrantLock而言,通过构造函数指定该是否是公平,默认是非公平...
  • 一、Java 线程有哪些状态 相关文章: Java线程的6种状态及切换(透彻讲解)(10w阅读量 90赞) ...[Java多线程 五]---JAVA锁有哪些种类(1.6w阅读量,详细) java 中的锁 -- 偏向锁、轻量级锁、自旋锁、重量...
  • 其实如果按照名称来说,大概以下名词:自旋 ,自旋的其他种类,阻塞,可重入 ,读写,互斥,悲观,乐观,公平,偏向, 对象,线程粗化,消除,轻量级,重量级, 信号量,独享...
  • Java有哪些锁,区别是什么

    千次阅读 2018-03-29 18:18:06
    公平:是指按照申请的顺序来获取, 非公平所:线程获取的顺序不一定按照申请的顺序来的。 //默认是不公平,传入true为公平,否则为非公平 ReentrantLock reentrantLock = new ReetrantLock(); ...
  • 什么是Java中的公平

    千次阅读 2018-05-05 23:13:46
    一直想分析下公平和非公平Java中的实现。公平(Fair):加锁前检查是否排队等待的线程,优先排队等待的线程,先来先得非公平(Nonfair):加锁时不考虑排队等待问题,直接尝试获取,获取不到自动到...
  • Java实现的几种方式

    千次阅读 2021-03-16 22:41:00
    学习多线程避不开的两个问题,Java提供了synchronized关键字来同步方法和代码块,还提供了很多方便易用的并发工具类,例如:LockSupport、CyclicBarrier、CountDownLatch、Semaphore…没有想过自己实现一个呢?...
  • 最近一些小伙伴会问我一些关于并发相关的问题,在与他们的沟通中,我发现他们对锁的概念很模糊。这部分基础概念的缺失导致他们写的程序经常死锁,还无法bebug。虽然在网上很多资料,但我还是用一些比较通俗易懂...
  • java锁

    千次阅读 2021-04-24 09:15:02
    多线程竞争同步资源的流程细节没有区别 1.不住资源 多个线程中只有一个能修改资源,其他线程重试 2.同意下城执行同步资源时自动获取资源 偏向 3.多个线程竞争同步资源时,没有获取资源的线程自旋等...
  • Java乐观%悲观

    千次阅读 2021-11-16 09:30:59
    尝试去获得一把,无论获得还是无法获得立刻返回True/False 不会阻塞进程,使用此可在获取的过程中完成其它操作。如下例子演示了在线程1获得了之后,线程2使用trylock()可以初始化一些参数,缺点是这里比较...
  • Java四种及分布式的初解

    千次阅读 2019-06-04 10:24:23
    乐观是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上,但是在更新的时候会判断一下在此期间别人没有去更新这个数据,采取在写时先读出当前版本号,...
  • java中的(一)(的介绍)

    万次阅读 多人点赞 2018-08-23 10:08:50
    了解了markword结构,助于后面了解java锁的加锁解锁过程; 小结 前面提到了java的4种锁,他们分别是重量级锁、自旋锁、轻量级锁和偏向锁,  不同的锁不同特点,每种锁只有在其特定的场景下,才会出色...
  • java中的锁有哪几种

    万次阅读 2018-01-02 15:50:57
    非公平是指多个线程获取的顺序并不是按照申请的顺序,可能后申请的线程比先申请的线程优先获取可能,会造成优先级反转或者饥饿现象。对于Java ReentrantLock而言,通过构造函数指
  • java中的

    千次阅读 2021-03-08 07:28:05
    synchronized 获取的,在方法抛出异常的时候会自动解锁ReentrantLock 获取的,异常的时候也不会自动释放!调用wait()时,会释放。调用sleep()时,不会释放。Lock接口api:lock(), unlock().......
  • 并发操作之——java多线程常用的

    千次阅读 2021-09-07 19:29:21
    并发操作之——java多线程常用的并发操作前言一、共享二、互斥三、死锁1、偏向2、轻量3、重量级总结 前言 并发操作之——java多线程常用的。 一、共享 也叫S/读,能查看但无法修改和删除的一...
  • java的乐观和悲观

    千次阅读 2020-06-23 22:05:14
    乐观:对于并发操作产生的线程安全问题持乐观态度,认为自己在使用数据时不会别的线程修改数据,所以不会添加,只是在更新数据的时候去判断之前没有别的线程更新了这个数据,如果这个数据没有被更新,当前...
  • Java中的和数据库的

    千次阅读 2019-04-05 10:33:15
    面试中经常被问到Java中有锁,数据库中也有锁,这两个中的都是怎么分类的呢?两篇博客关于这个问题讲的特别好,详见下文~ Java中的 在读很多并发文章中,会提及各种各样如公平,乐观等等,这篇...
  • Java中的详解

    千次阅读 多人点赞 2021-04-10 12:52:25
    目录 Lock和synchronized ... Lock和synchronized,这两个是最创建的,他们都可以达到线程安全的目的,但是使用和功能上较大不同 Lock不是完全替代synchronized的,而是当使用synchron...
  • Java多线程 -

    千次阅读 2022-03-28 15:31:49
    Java多线程 - 三性 可见性 指的是线程之间的可见性,一个线程对状态的修改,对其他线程是可见的。在 Java中 volatile、synchronized 和 final 实现可见性。 原子性 如果一个操作是不可分割的,我们则称之为...
  • Java 读写降级

    千次阅读 2019-06-18 19:47:03
    降级 :是指保持住当前的写(已拥有),再获取读,随后释放写的过程。 1.降级的用途 举一个场景:数据不常变化,多个线程可以对数据进行并发的处理,如果当前线程感知数据变化,则进行数据处理操作,而...
  • java的面试题

    千次阅读 2022-02-15 21:41:10
    1. synchronized(悲观、同步) synchronized关键字:表示“同步”的。它可以对“多行代码”进行“同步”——将多行代码当成是一...1.1synchronized几种使用方式: a).同步代码块【常用】 b).同步方法【常
  • java中的各种

    千次阅读 2022-04-02 19:51:02
    java中的各种详细介绍 转自:https://tech.meituan.com/2018/11/15/java-lock.html Java提供了种类丰富的,每种因其特性的不同,在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码(本文中的源码...
  • java锁升级过程

    万次阅读 多人点赞 2020-03-14 20:32:17
    java中对象锁有4种状态:(级别从低到高) 1.无锁状态 2.偏向状态 3.轻量级状态 4.重量级状态 对象头分两部分信息,第一部分用于存储哈希码、GC分代年龄等,这部分数据被称为"Mark Word"。在32位的HotSpot...
  • 阿里面试失败后,一气之下我图解了Java中18把

    万次阅读 多人点赞 2021-06-17 23:21:47
    升级(无锁|偏向|轻量级|重量级优化技术(粗化、消除) 乐观和悲观 悲观 悲观对应于生活中悲观的人,悲观的人总是想着事情往坏的方向发展。 举个生活中的例子,假设厕所只有一个...
  • JVM 对 Java 的原生做了哪些优化?

    千次阅读 2019-11-20 16:51:49
    由于 Java 层面的线程与操作系统的原生线程映射关系,如果要将一个线程进行阻塞或唤起都需要操作系统的协助,这就需要从用户态切换到内核态来执行,这种切换代价十分昂贵,很耗处理器时间,现代 JDK 中做了大量的...
  • 【深度解析】Java中的乐观、悲观

    千次阅读 多人点赞 2020-03-06 10:04:22
    有哪些加锁的方式呢? 我今天就简单聊一下乐观和悲观,他们对应的实现 CAS ,Synchronized,ReentrantLock CAS(Compare And Swap 比较并且替换)是乐观的一种实现方式,是一种轻量级...
  • Java中的自旋

    万次阅读 多人点赞 2018-10-25 22:20:41
    自旋(spinlock):是指当一个线程在获取的时候,如果已经被其它线程获取,那么该线程将循环等待,然后不断的判断是否能够...Java如何实现自旋? 下面是个简单的例子: public class SpinLock { priva...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 658,710
精华内容 263,484
关键字:

java有哪些锁

java 订阅