一、公平锁/非公平锁
公平锁
在并发环境下,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入等待队列,然后按照FIFO
的规则进行等待。
非公平锁
一上来就获取锁,如果失败,再采用公平锁的方式。
二、可重入锁/递归锁
这两个锁其实是一个锁的两种叫法,本质上就是一种锁。
指的是同一线程在外层函数获取到锁之后,内层函数仍可用该锁。举个例子:A方法中调用了B方法,两个方法使用同一把锁,在A中如果获取到锁之后,执行到B的时候就会自动获取锁。
三、自旋锁
指尝试获取锁的线程不会立即阻塞,而是循环的方式尝试获取锁,这样的好处是减少线程上下文切换带来的性能消耗,缺点是消耗CPU。原子类的实现就是典型的自旋锁使用场景。
==手写一个自旋锁==
package com.fzkj.juc.spinlock;
import java.util.concurrent.atomic.AtomicReference;
/**
* @DESCRIPTION 自己手写一个自旋锁的实现
*/
public class SpinLock {
private AtomicReference<Thread> reference = new AtomicReference<>();
private Thread currentThread = null;
public void lock(){
// 获取当前线程
currentThread = Thread.currentThread();
while (!reference.compareAndSet(null, currentThread)){}
System.out.println(currentThread.getName() + "\t 获得锁");
}
public void unLock(){
if (currentThread != null){
reference.compareAndSet(currentThread, null);
System.out.println(currentThread.getName() + "\t 释放锁");
}
}
}
package com.fzkj.juc.spinlock;
import java.util.concurrent.TimeUnit;
/**
* @DESCRIPTION 自己写的自旋锁测试
*/
public class SpinLockDemo {
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
new Thread(() -> {
spinLock.lock();
test("BBB");
spinLock.unLock();
}, "BBB").start();
// 为方便显示效果,sleep 1秒。让BBB线程先执行
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
spinLock.lock();
test("AAA");
spinLock.unLock();
}, "AAA").start();
}
public static void test(String name){
System.out.println(name + "\t 进来了");
}
}
四、读写锁
独占锁(写锁):指该锁只能被一个线程持有。ReentrantLock
和synchronized
都是独占锁。
共享锁(读锁):指该锁可以被多个线程持有。
缓存demo:
/**
* @DESCRIPTION 缓存小工具
*/
public class SimpleCache {
private volatile Map<String, Object> map = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public void put(String key, Object value){
// 使用写锁,保证写操作的一致性
lock.writeLock().lock();
try{
System.out.println(key + "\t 正在写入 \t" + key);
map.put(key, value);
System.out.println(key + "\t 写入完成");
}finally {
lock.writeLock().unlock();
}
}
public Object get(String key){
// 使用读锁,不用保证读
lock.readLock().lock();
try{
System.out.println(key + "\t 正在读取");
Object o = map.get(key);
System.out.println(key + "\t 读取完成 \t" + o);
return o;
}finally {
lock.readLock().unlock();
}
}
public void clear(){
map.clear();
}
}
五、轻量级锁
轻量级锁的目标是,减少无实际竞争情况下,使用重量级锁产生的性能消耗。
使用轻量级锁时,不需要申请互斥量(mutex,依赖操作系统,成本高),仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。
六、偏向锁
在轻量级锁的基础上,如果不仅没有竞争,连线程都只有一个,那么维护轻量级锁也是一种损耗。偏向锁的目标是,减少无竞争且只有一个线程使用锁的情况下,使用轻量级锁产生的性能消耗。轻量级锁每次申请、释放锁都至少需要一次CAS,但偏向锁只有初始化时需要一次CAS。
“偏向”的意思是,偏向锁假定将来只有第一个申请锁的线程会使用锁(不会有任何线程再来申请锁),因此,只需要在Mark Word中CAS记录owner(本质上也是更新,但初始值为空),如果记录成功,则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本的直接获得锁;否则,说明有其他线程竞争,膨胀为轻量级锁。