java-锁

java / 2022-08-22
0 1,490

一、公平锁/非公平锁

公平锁

在并发环境下,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入等待队列,然后按照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 进来了");
    }

}

四、读写锁

独占锁(写锁):指该锁只能被一个线程持有。ReentrantLocksynchronized都是独占锁。

共享锁(读锁):指该锁可以被多个线程持有。

缓存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就可以零成本的直接获得锁;否则,说明有其他线程竞争,膨胀为轻量级锁。
锁分配过程