当前位置: 首页 > 科技观察

Java中的21种锁,图文详解

时间:2023-03-12 03:19:35 科技观察

本文主要内容如下:帮你总结好锁:序号锁名应用1乐观锁CAS2悲观锁synchronized,vector,hashtable3spin锁CAS4可重入锁synchronized,Reentrantlock,Lock5读写锁ReentrantReadWriteLock,CopyOnWriteArrayList,CopyOnWriteArraySet6公平锁Reentrantlock(true)7非公平锁synchronized,reentrantlock(false)8共享锁ReentrantReadWriteLockreadlock9exclusivelocksynchronized,vector,hashtable,ReentrantReadwritelock10WriteLock重量级锁synchronized11轻量级锁优化技术12偏向锁锁优化技术13分段锁concurrentHashMap14互斥锁synchronized15同步锁synchronized16死锁相互请求对方资源17锁粗化锁优化技术18锁消除锁优化技术。一、乐观锁乐观锁乐观锁是一种乐观的思想。假设当前环境是读多写少,遇到并发写的概率比较低。读取数据时,认为其他线程不会修改(所以没有锁)。写入数据时,判断当前值是否与期望值相同,如果相同则更新(更新时加锁保证原子性)。Java中乐观锁:CAS,比较替换,比较当前值(主存中的值),是否与期望值相同(当前线程中的值,主存中值的副本),如果相同则更新,否则继续进行CAS操作。如上图所示,可以同时进行读操作,其他线程不能在读的同时进行写操作。2.悲观锁悲观锁悲观锁是一种悲观思想,即认为写多读少,并发写的可能性大。它每次去取数据的时候都认为其他线程会修改它,所以每次读写数据的时候都假设其他线程会修改它,所以每次读写数据都会被加锁。当其他线程要读写这个数据时,会被这个线程阻塞,直到这个线程释放锁,其他线程才获取到锁。Java中的悲观锁:同步修改方法和方法块,ReentrantLock。如上图所示,只有一个线程可以进行读写操作,其他线程的读写操作是不能进行的。3、自旋锁自旋锁是一种技术:为了让线程等待,我们只需要让线程执行一个忙循环(自旋)即可。现在绝大多数的个人电脑和服务器都是多通道(核心)处理器系统。如果物理机有多个处理器或处理器核心,可以同时并行执行两个或多个线程,让后面请求锁的线程“稍等”,但不放弃处理器执行时间,看看持有锁的线程会不会很快释放锁。自旋锁的优点:避免线程切换的开销。挂起线程和恢复线程的操作需要在内核态完成,这些操作给Java虚拟机的并发性能带来了很大的压力。自旋锁的缺点:占用处理器时间,如果时间长了,会白白消耗处理器资源,不会做任何有价值的工作,造成性能上的浪费。因此,自旋等待时间必须有一定的限度。如果自旋超过了限制次数,仍然无法获得锁,则应该使用传统的方法挂起线程。自旋次数默认值:10次,可以使用参数-XX:PreBlockSpin自行更改。自适应自旋:自适应是指自旋时间不再是固定的,而是由同一把锁上的前一次自旋时间和锁拥有者的状态决定的。有了自适应自旋,随着程序运行时间的增加和性能监控信息的不断完善,虚拟机对程序锁状态的预测会越来越准确。Java中的自旋锁:CAS操作比较操作失败后自旋等待。4、可重入锁(recursivelock)可重入锁可重入锁是一种技术:任何线程在获得锁后都可以再次获得锁,而不会被锁阻塞。可重入锁的原理:锁的获取和释放是通过结合自定义同步器来实现的。再次获取锁:判断获取锁的线程是否为当前占用锁的线程,如果是则再次获取成功。获取锁后,计数递增,释放锁:释放锁时,计数递减。Java中的可重入锁:ReentrantLock,同步修饰的方法或代码段。可重入锁的作用:避免死锁。面试题1:如果加了两把可重入锁,但只释放了一把,会怎样?答:程序卡住,线程出不来。也就是说,我们申请了好几把锁之后,需要释放好几把锁。面试题2:如果只加一把锁,释放两次会怎样?答:会报错,java.lang.IllegalMonitorStateException。5.读写锁读写锁是一种技术:它是通过ReentrantReadWriteLock类实现的。为了提高性能,Java提供了读写锁。读锁用于读,写锁用于写。灵活控制。如果没有写锁,读是非阻塞的,一定程度上改进了程序。执行效率。读写锁分为读锁和写锁。多个读锁不互斥,读锁和写锁互斥。这是jvm自己控制的。读锁:允许多个线程获取读锁,同时访问同一个资源。读锁和写锁:只允许一个线程获取写锁,不允许同时访问同一个资源。写锁的使用方法:/***创建一个读写锁*是一个读写一体的锁。使用时需要转换*/privateReentrantReadWriteLockrwLock=newReentrantReadWriteLock();acquirereadlockandreleasereadlock//获取读锁rwLock.readLock().lock();//释放读锁rwLock.readLock()。开锁();获取写锁和释放写锁//创建写锁rwLock.writeLock().lock();//释放写锁rwLock.writeLock().unlock();Java中的读写锁:ReentrantReadWriteLock6.公平锁Fairlock公平锁是一种思想:多个线程按照申请锁的顺序获取锁。在并发环境下,每个线程都会先查看锁维护的等待队列。如果当前等待队列为空,就会占用锁。如果等待队列不为空,则按照先进先出的原则加入等待队列的尾部,从队列开始。获取里面的线程,然后占用锁。7.非公平锁非公平锁非公平锁是一种思想:线程尝试获取锁,如果不能,则使用公平锁。多个线程获取锁的顺序并不是先到先得的顺序。有可能后申请锁的线程比先申请的线程先获得锁。优点:非公平锁的性能要高于公平锁。缺点:可能造成线程饥饿(一个线程长时间无法获取锁)。Java中的非公平锁:synchronized是一种非公平锁。ReentrantLock通过构造函数指定锁是公平的还是非公平的。默认是不公平的。8.共享锁共享锁共享锁是一种思想:多个线程可以共享方式获取读锁和持有锁。乐观锁和读写锁的同义词。Java中使用的共享锁:ReentrantReadWriteLock。9、独占锁独占锁独占锁是一种思想:只有一个线程才能以独占的方式获取锁并持有锁。悲观锁和互斥锁的同义词。Java中使用的独占锁:synchronized、ReentrantLock10、重量级锁重量级锁重量级锁是一种称呼:synchronized是通过对象内部的一个监视器锁(monitor)来实现的,监视器锁本身依赖于底层的互斥锁操作系统已实施。操作系统需要从用户态切换到核心态来切换线程,代价很大。这种依赖于操作系统MutexLock来实现的锁称为重量级锁。为了优化synchonized,引入了轻量级锁和偏向锁。Java中的重量级锁:synchronized11、轻量级锁轻量级锁轻量级锁是JDK6中加入的一种锁优化机制:轻量级锁使用CAS操作消除同步不竞争互斥锁。轻量级是相对于使用操作系统互斥体实现的重量级锁而言的。轻量级锁减少了传统重量级锁使用操作系统互斥锁的性能消耗,无需多线程竞争。如果有两个以上的线程竞争同一个锁,那么轻量级锁就失效了,必须扩展为重量级锁。优点:如果没有竞争,通过CAS操作成功避免了使用互斥锁的开销。缺点:如果存在竞争,除了互斥量本身的开销外,还会产生额外的CAS操作开销。因此,在竞争的情况下,轻量级锁比传统的重量级锁慢。12、偏向锁偏向锁是JDK6中加入的一种锁优化机制:在没有竞争的情况下,整个同步被淘汰,连CAS操作都不做。偏心指的是偏心,就是说锁会偏向第一个获得它的线程。如果在接下来的执行过程中,锁还没有被其他线程获取到,持有偏向锁的线程就会被永远锁住。不需要进一步同步。持有偏向锁的线程每进入一个与这个锁相关的同步块,虚拟机就不能再进行任何同步操作(比如对MarkWord的加锁、解锁、更新等操作)。优点:取消了整个同步,甚至不进行CAS操作,优于轻量级锁。缺点:如果程序中的大部分锁总是被多个不同的线程访问,那么偏向锁就显得多余了。13.段锁段锁段锁是一种机制:说明段锁最好的例子就是ConcurrentHashMap。ConcurrentHashMap的原理:它内部细分了若干个小的HashMap,称为段(Segment)。默认情况下,一个ConcurrentHashMap被进一步细分为16段,这就是锁的并发。如果需要在ConcurrentHashMap中添加一个key-value,不是锁定整个HashMap,而是先根据hashcode获取key-value应该存放的段,然后锁定该段,完成put操作。在多线程环境下,如果多个线程同时进行put操作,只要添加的key-value不存储在同一个段中,线程之间就可以真正并行。线程安全:ConcurrentHashMap是一个Segment数组,Segment是通过继承ReentrantLock来加锁的,所以每次需要加锁的操作都会加锁一个Segment,所以只要每个Segment都是线程安全的,全局线程安全14.互斥锁互斥锁是悲观锁和独占锁的同义词,表示一个资源只能被一个线程访问,其他线程不能访问。读读互斥读写互斥写读互斥写写互斥同步锁Java中:synchronized15、同步锁synchronizationlock同步锁是互斥锁的同义词,表示并发执行的多个线程,在Only一次允许一个线程访问共享数据。Java中的同步锁:synchronized16,deadlockdeadlock死锁是一种现象:比如线程A持有资源x,线程B持有资源y,线程A等待线程B释放资源y,线程B等待线程A释放资源x,如果两个线程都没有释放自己持有的资源,那么两个线程都无法获得对方的资源,就会造成死锁。Java中的死锁不能自己打破,所以线程死锁后,线程无法响应。所以一定要注意程序的并发场景,避免死锁。17.锁粗化锁粗化是一种优化技术:如果一系列连续的操作重复加锁和解锁同一个对象,甚至加锁操作出现在循环体中,即使是真的没有线程竞争,频繁相互排除同步操作会导致不必要的性能损失,因此采用了一种解决方案:将加锁的范围扩大(粗化)到整个操作序列之外,这样加锁和解锁的频率就会大大降低,从而减少性能损失。18.锁消除锁消除是一种优化技术:就是杀死锁。当Java虚拟机在运行时发现某些共享数据不会被线程竞争时,可以进行锁消除。那么如何判断共享数据不会被线程竞争呢?使用逃逸分析技术:分析对象的范围。如果对象在方法A中定义,并作为参数传递给方法B,则称为方法逃逸;如果被其他线程访问,则称为线程逃逸。堆上的某个数据不会逃逸被其他线程访问,所以可以把它当作栈上的数据,对线程来说是私有的,不需要同步锁。19、synchronizedsynchronized是Java中的一个关键字:用来修饰方法和对象实例。属于排他锁、悲观锁、可重入锁、非公平锁。1、作用于实例方法时,对象实例(this)被锁定;2、作用于静态方法时,加锁的是Class类,相当于对该类的全局锁,会锁住对该方法Thread的所有调用;3.当synchronized作用于一个非NULL对象实例时,所有锁定该对象的代码块都被锁定。它有多个队列。当多个线程一起访问一个对象监视器时,对象监视器会将这些线程存放在不同的容器中。每个对象都有一个监视器对象。加锁就是为了争夺监听对象。代码块锁定是通过在代码块前后添加monitorenter和monitorexit指令来实现的。方法加锁是通过一个标志位来判断的。20、Lock和synchronized的区别自动传输和手动传输的区别Lock:是Java中的一个接口,可重入锁,悲观锁,排它锁,互斥锁,同步锁。1.Lock需要手动获取和释放锁。就像自动变速器和手动变速器的区别2.Lock是一个接口,而synchronized是Java中的一个关键字,synchronized是一种内置的语言实现。3、synchronized在异常发生时会自动释放线程占用的锁,所以不会造成死锁现象;而Lock,如果出现异常,如果不通过unLock()主动释放锁,很可能会造成死锁现象,所以在使用Lock时需要在finally块中释放锁。4.Lock可以让等待锁的线程响应中断,而synchronized不行。使用synchronized时,等待线程会一直等待,无法响应中断。5、通过Lock可以知道锁是否已经成功获取,而synchronized不能。6.Lock通过实现读写锁可以提高多线程进行读操作的效率。synchronized的优点:足够清晰和简单。当只需要基本的同步功能时,使用synchronized。Lock应该保证在finally块中释放锁。如果使用synchronized,JVM保证即使出现异常,锁也会自动释放。使用Lock时,Java虚拟机很难知道具体的线程锁持有哪些锁对象。21、ReentrantLock和同步区Lock、ReentrantLock、shnchronziedReentrantLock是Java中的类:继承自Lock类,可重入锁、悲观锁、排他锁、互斥锁、同步锁。关键点都是一样的:1.主要解决了如何安全访问共享变量的问题。2.都是可重入锁,也叫递归锁。同一个线程可以有一个锁。3.保证线程安全的两大特性:可见性、原子性。不同点:1.ReentrantLock就像手动挡的车,需要显式调用lock和unlock方法,synchronized隐式获取释放锁。2、ReentrantLock可以响应中断,synchronized不能响应中断。ReentrantLock为处理锁不可用提供了更高的灵活性。3、ReentrantLock是API层面的,synchronized是JVM层面的。4.ReentrantLock可以实现公平锁,Unfair锁,默认非公平锁,synchronized是非公平锁,不可更改。5.ReentrantLock可以通过Condition绑定多个条件