乐观锁和悲观锁
乐观锁:在获取锁时预期锁的竞争不太激烈,在执行任务时先不进行加锁,或者是少加锁(或者是等到需要加锁的时候再加锁)
悲观锁:在获取锁时预期锁的竞争非常激烈,在执行任务前必须进行加锁操作,加锁之后才能去执行任务。
我们用现实的一个例子来对此进行说明:
对乐观锁的和悲观锁的总计而言就是根据对锁的态度执行加锁的操作。
轻量级锁和重量级锁
轻量级锁:加锁的过程比较简单,需要消耗的资源相对较少,典型的就是在用户态完成加锁操作(在JAVA层面就能完成的加锁操作)
重量级锁:加锁的过程比较复杂,需要消耗的资源相对较多,典型的是在内核态完成的加锁操作
而乐观锁的锁策略是能不加锁就加锁,相对而言消耗的资源比较少,所以乐观锁是一种轻量级锁
悲观锁的策略是在执行任务前必须加锁,这样消耗的资源就比较多,所以悲观锁是一种重量级锁
自旋锁和挂起等待锁
自旋锁:不断去询问锁资源是否被释放,一旦锁资源被释放,就直接获取锁资源
挂起等待锁:不主动询问锁资源,而是通过系统的调度去竞争锁资源
我们用现实的一个例子来对概念进行解释:
我跟朋友越好了吃饭,当我到他家楼下的时候他还没有下楼,这时候我给他打电话催促他抓紧下来,他说马上就下来了,过了一小会儿他还没有下来,我继续打电话催促,(不断询问)直到他说他下来了,我告诉他我的具体位置(获取锁资源)
而挂起等待锁的实际场景是,我到他楼下之后不催他下来,而是在那玩手机,等他下来之后等他告诉我,(系统调度通知)然后我告诉他我的具体位置
自旋锁的优缺点:
①在获取锁资源之前的不断询问锁资源是否被释放是纯用户态的操作,是一种轻量级锁
②由于不断询问锁资源是否被释放,当锁资源被释放后能第一时间获取到获取到锁
③不断的循环相对而言消耗资源比较多(缺)。
在实际的业务场景中,我们可以通过控制自旋的次数来降低资源的消耗
挂起等待锁的优缺点:
①通过线程阻塞和就绪状态的切换来获取锁资源
②锁资源一旦释放无法第一时间获取锁
③是通过系统内核来进行处理的
自旋锁是一种轻量级锁的具体体现,挂起等待锁是一种重量级锁的具体体现
读写锁和普通互斥锁
读写锁:
读锁:在进行读操作时加锁(共享锁),多个锁可以共存,同时加多个读锁并不相互影响。
写锁:在进行写操作时加锁(排它锁),只能有一个写锁在执行任务,和其他锁是互斥的
写锁和写锁不能共存
写锁和读锁不能共存
读锁和读锁可以共存
为什么要有读写锁?
我们在进行读操作时会有激烈的锁竞争,但是读操作按照真实的业务场景彼此之间是不相互影响的,所以所有的读都可以并发执行(高效率),当一个操作是读操作,携带的锁是读锁,这时候可以直接进行读取,不产生锁竞争,进而降低了资源的消耗,同时提高了程序运行的效率。如果这时候的操作是写操作时,让它等待。
读写锁相当于在给锁打上了'读'和'写'的标签,在高并发读的应用场景中,锁竞争会大大降低,进而提高程序的运行效率
互斥锁:多个锁之间存在锁竞争,只有当一个线程释放锁之后,别的线程才能来竞争锁资源,我们之前几乎所有的锁都是互斥锁
公平锁和非公平锁
公平锁:讲究先来后到,先排队的先获取锁,后排队的后获取锁
非公平锁:多个线程一起竞争锁资源,谁抢到就是谁的
如果想要创建公平锁,需要额外消耗一些资源,比如锁的实现过程要记录哪个线程先来后到,需要一种数据结构把线程组织起来,这也是一种资源的消耗,这就意味着公平锁的效率必然会受到影响。
而非公平锁就没有这种资源的消耗,所以在java和其他用锁的环境中,默认的锁资源都是非公平锁,而java中的juc中有一个类专门实现了公平锁
synchronized是一把非公平锁
可重入锁和不可重入锁
可重入锁:对于同一个线程的同一把锁,允许上锁多次而不造成死锁。
不可重入锁:对于同一把锁多次不允许多次上锁
我们需要注意的是:对于同一把锁多次上锁同时必须进行多次解锁,否则其他线程无法获取该锁
我们从这六个概念的角度来分析synchronized是一把怎样的锁?
- 既是乐观锁,又是悲观锁
- 既是轻量级锁,又是重量级锁
- 既是自旋锁,又是挂起等待锁
- 是普通互斥锁
- 是非公平锁
- 是可重入锁