1.JAVA锁有哪些种类,读写以及区别
2.Java并发编程解析 | 基于JDK源码解析Java领域中并发锁之StampedLock锁的码分设计思想与实现原理 (三)
3.通俗易懂 悲观锁、乐观锁、析读写锁可重入锁、读写自旋锁、码分偏向锁、析读写锁预约类app源码轻量/重量级锁、读写读写锁、码分各种锁及其Java实现!析读写锁
4.java中的读写非公平锁不怕有的线程一直得不到执行吗
5.带你彻底理解Java中的21种锁
6.JUC可重入读写锁ReentrantReadWriteLock的锁获取和释放流程
JAVA锁有哪些种类,以及区别
常见的码分Java锁有下面这些:公平锁/非公平锁
可重入锁
独享锁/共享锁
互斥锁/读写锁
乐观锁/悲观锁
分段锁
偏向锁/轻量级锁/重量级锁
自旋锁
这些分类并不是全是指锁的状态,有的析读写锁指锁的特性,有的读写指锁的设计,下面总结的码分内容是对每个锁的名词进行一定的解释。
公平锁/非公平锁
公平锁是析读写锁指多个线程按照申请锁的顺序来获取锁。
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。
对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。
可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。
对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁。
对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。
synchronized void setA() throws Exception{Thread.sleep();
setB();
}synchronized void setB() throws Exception{
Thread.sleep();
}
上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。独享锁/共享锁
独享锁是指该锁一次只能被一个线程所持有。
共享锁是指该锁可被多个线程所持有。
对于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的时候,可就是dopdf 源码获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。
分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
偏向锁/轻量级锁/重量级锁
这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
自旋锁
在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
Java并发编程解析 | 基于JDK源码解析Java领域中并发锁之StampedLock锁的设计思想与实现原理 (三)
在并发编程领域,核心问题涉及互斥与同步。互斥允许同一时刻仅一个线程访问共享资源,同步则指线程间通信协作。多线程并发执行历来面临两大挑战。为解决这些,设计原则强调通过消息通信而非内存共享实现进程或线程同步。
本文探讨的关键术语包括Java语法层面实现的锁与JDK层面锁。Java领域并发问题主要通过管程解决。内置锁的粒度较大,不支持特定功能,因此JDK在内部重新设计,引入新特性,实现多种锁。基于JDK层面的锁大致分为4类。
在Java领域,AQS同步器作为多线程并发控制的基石,包含同步状态、等待与条件队列、独占与共享模式等核心要素。JDK并发工具以AQS为基础,qquickwidget 源码实现各种同步机制。
StampedLock(印戳锁)是基于自定义API操作的并发控制工具,改进自读写锁,特别优化读操作效率。印戳锁提供三种锁实现模式,支持分散操作热点与削峰处理。在JDK1.8中,通过队列削峰实现。
印戳锁基本实现包括共享状态变量、等待队列、读锁与写锁核心处理逻辑。读锁视图与写锁视图操作有特定队列处理,读锁实现包含获取、释放方式,写锁实现包含释放方式。基于Lock接口的实现区分读锁与写锁。
印戳锁本质上仍为读写锁,基于自定义封装API操作实现,不同于AQS基础同步器。在Java并发编程领域,多种实现与应用围绕线程安全,根据不同业务场景具体实现。
Java锁实现与运用远不止于此,还包括相位器、交换器及并发容器中的分段锁。在并发编程中,锁作为实现方式之一,提供线程安全,但实际应用中锁仅为单一应用,提供并发编程思想。
本文总结Java领域并发锁设计与实现,重点介绍JDK层面锁与印戳锁。文章观点及理解可能存在不足,欢迎指正。技术研究之路任重道远,希望每一份努力都充满价值,未来依然充满可能。
通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现!
本文旨在为Java新手提供一个易于理解的锁概念整合,消除对各种锁术语的恐惧感,并浅尝辄止地解释它们的底层实现。请注意,以下内容将仅使用HTML标签进行展示。
Java锁有多种类型,但不必过于担心它们之间的枫叶源码区别。例如,一个锁可以是悲观锁、可重入锁、公平锁等多种属性的结合。类比于一个人的身份,一个人可以是医生、健身爱好者、游戏玩家等多种角色,并非互相排斥。
在Java中,锁大致可以分为两类:自动加锁(如`synchronized`关键字)和显式锁(如`Lock`接口)。`synchronized`是一种自动管理锁的机制,适合日常使用,而`Lock`接口则提供更灵活的锁管理,适用于复杂场景。
`ReentrantLock`、`ReadLock`和`WriteLock`是`Lock`接口的重要实现类,分别对应可重入锁、读锁和写锁。`ReadWriteLock`是一个工厂接口,其主要实现类`ReentrantReadWriteLock`包含了用于读和写的静态内部类,这些类都实现了`Lock`接口。
悲观锁和乐观锁是根据对并发情况的假设来分类的。悲观锁假设每次获取数据时,其他线程可能会修改数据,因此每次获取数据时都会上锁。乐观锁则假设数据在读取时不会被修改,但在更新数据前会检查数据是否被他人修改过。
乐观锁的基础是`CAS`(Compare-and-Swap)操作。通过`CAS`,可以实现一个乐观锁,允许多个线程同时读取数据,但只有一个线程可以成功更新数据。`CAS`操作在硬件层面实现原子性,从而达到锁的效果,但无需实际的锁操作,故被称为无锁编程。
`自旋锁`是一种锁机制,通过无限循环(`while(true)`)来尝试获取锁,而无需阻塞等待。尽管自旋锁在某些场景下可以提高性能,但在大多数情况下,它会导致CPU资源的浪费,因此在Java中没有直接的自旋锁类。
`synchronized`锁的升级机制从偏向锁、轻量级锁到重量级锁,这是一系列优化策略,旨在提高性能和减少锁的竞争。轻量级锁实际上是一种自旋锁,用于处理锁的竞争。
`可重入锁`允许同一个线程多次获取同一把锁,无需阻塞。大部分Java锁都是可重入锁,包括`synchronized`关键字和`ReentrantLock`等。
`公平锁`与`非公平锁`的概念区分了锁分配的策略,公平锁确保了线程按照申请锁的顺序获得锁,而非公平锁则允许后申请的线程在某些情况下先获取锁。
`可中断锁`允许线程在等待锁时响应中断信号,提供了一种灵活的机制来控制线程的等待状态。
读写锁则将锁分为读锁和写锁,允许多个线程同时读取数据,但在写操作时需要独占锁,以避免数据不一致的问题。
通过了解这些锁类型及其特性,可以在实际开发中根据需求选择最合适的锁机制,提高程序的性能和稳定性。
java中的非公平锁不怕有的线程一直得不到执行吗
首先来看公平锁和非公平锁,我们默认使用的锁是非公平锁,只有当我们显示设置为公平锁的情况下,才会使用公平锁,下面我们简单看一下公平锁的源码,如果等待队列中没有节点在等待,则占有锁,如果已经存在等待节点,则返回失败,由后面的程序去将此线程加入等待队列通过上面的代码,我们可以推断,当使用公平锁的情况下,并且同一个线程的执行时间较长时,线程内部进行了多次的锁的获取和释放,效率非常低下,可以参加Lesson8中的demo:
demo Lesson8LockIntPerform:在使用ReentrantLock加非公平锁的情况下个线程循环下单数为:
demo Lesson8LockIntPerform:在使用ReentrantLock加非公平锁的情况下个线程循环下单数为:
demo Lesson8LockFairIntPerform:在使用ReentrantLock加公平锁的情况下个线程循环下单数为:
demo Lesson8LockFairIntPerform:在使用ReentrantLock加公平锁的情况下个线程循环下单数为:
上面的demo中,在使用公平锁的情况下性能明显降低,非公平锁的性能是公平锁性能的几十倍以上,这和公平锁每次试图占有锁时,都必须先要进等待队列,按照FIFO的顺序去获取锁,因此在我们的实验情景下,使用公平锁的线程进行了频繁切换,而频繁切换线程,性能必然会下降的厉害,这也告诫了我们在实际的开发过程中,在需要使用公平锁的情景下,务必要考虑线程的切换频率。
接下来我们来看一下读写锁,通过看读写锁的实现源码,我们可以发现,读锁和写锁共用同一个等待队列,那么在采用非公平锁的情况下,如果读锁的线程执行时间比较长,并且读锁的并发比较高,那么写锁的线程便永远都拿不到锁,那么实际的情况会不会是这样呢?
demo Lesson3WriteReadLock:此demo的读线程在不断的占用读锁,按照推论,写锁的线程是没有机会获取到锁的,但是实际情况是写锁的线程可以正常的获取到锁,那么是什么原因使得写锁的线程可以获取到锁的了?通过查看源代码,会发现有这样的一个方法:
上面的方法,实现了一个新的读线程获取锁的中断,它会读取等待队列中下一个等待锁的线程,如果它是获取写锁的线程,那么此方法返回为真,调用它的程序会把这个试图获取读锁的线程加入到等待队列,从而终止了读线程一直都在占有锁的情况。
带你彻底理解Java中的种锁
理解Java中的锁机制,从乐观到悲观,自旋到可重入,每种都有其独特之处。首先,乐观锁(如CAS)假设读多写少,读取时无需加锁,只有在写入时才会检查并可能更新数据,确保一致性。悲观锁(如synchronized和ReentrantLock)则倾向于防止单线程修改,每次读写都会锁定资源,避免并发冲突。
自旋锁是让线程在等待锁时不断循环,直到获得。这在多核处理器环境下可能提高效率,但过长的自旋会导致资源浪费。自适应自旋进一步优化了这种策略,根据锁的持有情况动态调整等待策略。
可重入锁(如ReentrantLock和synchronized)允许线程在持有锁的情况下再次获取,避免死锁问题。然而,不当使用可能导致死锁或异常,例如加锁不匹配释放,需注意操作顺序。
读写锁(ReentrantReadWriteLock)区分读和写操作,提高多读场景的并发性能。公平锁与非公平锁的区别在于获取锁的顺序,公平锁按申请顺序,而非公平锁则可能导致线程饥饿。
共享锁和独占锁是资源访问模式的描述,前者允许多线程读,后者保证互斥写。重量级锁(synchronized)与轻量级锁和偏向锁是性能优化策略,减少切换开销,但存在竞争时性能各异。
最后,分段锁(如ConcurrentHashMap)通过分段管理,允许并发访问,而互斥锁(synchronized)和ReentrantLock确保同一时间只有一个线程访问共享资源。死锁和锁优化技术如粗化、消除则关乎并发性能和避免死锁。
JUC可重入读写锁ReentrantReadWriteLock的锁获取和释放流程
ReentrantReadWriteLock是Java中的一种实现读写锁的机制,它支持一个线程多次获取同一锁,类似于ReentrantLock的可重入特性。这意味着,一旦一个线程拥有一个锁,它能再次获取该锁而不会阻塞其他线程。 在ReentrantReadWriteLock的架构中,读线程与写线程的交互被严格管理。当一个线程获取了读锁,其他线程可以同时获得读锁,但无法获取写锁。当写锁被占用时,任何尝试获取写锁的线程会暂停等待,直到写锁释放。 锁的获取与释放流程与ReentrantLock相似。线程在需要锁时尝试获取,若锁未被占用,则顺利获取;否则,线程将进入阻塞状态,直到锁被释放。 在释放锁阶段,ReentrantReadWriteLock会根据锁的类型执行不同的策略。读锁的释放是即时的,而写锁的释放需要等待一段时间,确保在写入期间没有其他读线程尝试访问,从而提升并发性能,减少线程间的争抢。 然而,ReentrantReadWriteLock与乐观锁和悲观锁有所区别。乐观锁在读取时不加锁,仅在写入时加锁,而悲观锁则始终在读写时加锁,以确保资源独占。这三种机制在处理共享资源并发访问时采用不同的策略。 总结来说,ReentrantReadWriteLock是Java中一种巧妙的读写锁实现,它支持多线程同时读取共享资源且无冲突。在使用时,务必注意锁的类型,以实现有效的并发控制。读写锁ReadWriteLock的实现原理
理解读写锁的实现原理,首先明确几个关键概念。读写锁,顾名思义,可以同时支持读操作和写操作。读操作可以并行,而写操作则具有独占性。读写锁内部使用一个状态变量(如state)来表示锁的当前状态。
读写锁提供了几个核心方法:getReadLockCount()、getReadHoldCount()、getWriteHoldCount()和isWriteLocked()。getReadLockCount()返回读锁的总数量,getReadHoldCount()表示当前线程持有读锁的次数,getWriteHoldCount()则为写锁的持有次数,isWriteLocked()判断当前锁是否处于写锁状态。
实现原理源码分析:核心在于使用一个状态变量state来表示读写锁的状态。state的值可以是以下几种情况:0表示没有锁,1表示写锁,2表示读锁,3表示写锁与读锁同时存在。读锁和写锁之间存在兼容性,即写锁可以重入,读锁也同样可以重入。
写锁的加锁操作,当尝试加锁时,检查state是否为0(无锁状态),如果是,则将state设置为1(写锁状态),并返回成功。如果state已为1或3,则说明已有写锁存在,无法再加写锁,直接返回失败。
读锁的加锁操作,检查state是否为0(无锁状态)或2(已有读锁),如果是,则可成功加锁,将state设置为2(读锁状态),并返回成功。如果state为1(写锁状态)或3(写锁与读锁同时存在),则表示已有写锁存在,读锁无法加锁,返回失败。
写锁与读锁的释放操作,都是将state设置回0,表示锁已经被释放。释放操作后,系统会自动检查是否有其他线程可以加锁。
注意事项:在使用读写锁时,需要特别注意重入锁的情况。读锁和写锁都允许重入,即线程可以多次加锁,但在加锁前应先检查state,避免不必要的操作。
总结:读写锁的实现主要通过状态变量来管理锁的状态,通过方法调用控制锁的加锁和释放。理解状态变量的含义和操作方法是关键。在实际应用中,正确使用读写锁可以显著提高并发程序的性能。
:深入学习Java并发编程,可以参考《Effective Java》、《Java Concurrency in Practice》等书籍,同时关注Java官方文档关于读写锁的说明。
多线程之读写锁
读写锁是多线程编程中的重要组件,其作用在于管理并发访问资源时的读写操作。在java环境中,读写锁实现了一对互斥锁——读锁和写锁,既满足了资源的读取需求,又保证了数据在需要更改时的安全性。其核心规则是:写锁会排斥所有读写操作;读锁则仅排斥写操作,对读操作相互不产生冲突。
想象一下,当你正在阅读一本书时,其它同时阅读同一本书的人并不会影响你的阅读,而当你想要写笔记时,就需要等待他人阅读结束。这与读写锁的工作原理类似。在读写锁中,写操作会阻止任何写和读操作,而读操作则允许其他读操作同时进行,但会阻止写操作。
下面通过一段代码来直观展示读写锁的用法和特性。假设我们有3次写操作和2次读操作,运行代码后,可以看到写操作(如写入变量count1)会等待读操作结束(如读取变量count1),即写操作会阻断任何读写操作。而读操作(如读取变量count2)之间则可以交替进行,尽管它们都持有读锁,但它们不会阻断彼此。同时,写操作(如写入变量count2)会优先于后续的读操作(如读取变量count2)执行,表明读锁仅阻断写操作。
综上所述,读写锁在读多写少的应用场景中,通过其特性显著提高了性能。写锁严格禁止所有读写操作,而读锁则仅阻止写操作,从而在确保数据一致性的同时,允许多个读操作并发进行。因此,对于理解读写锁的原理和应用,今天分享的内容已经提供了全面的视角,包括基本用法和互斥原则的验证。掌握读写锁的知识,对于多线程编程和资源管理来说,将是一个重要的技术工具。