玩转高并发系列----锁(一)

锁的基本概念

  1. 可重入锁:指当一个线程获得锁A,进入互斥区域后,可以再次获取到锁A。而不是说一个线程获得锁A之后,还可以进入锁B。很显然,通常的锁都要设计成可重入的,否则就会发生死锁。 如下代码展示了锁的可重入性:
private int value;
private Lock lock = new ReentrantLock();
public void increment(){
    try {
        lock.lock();
        lock.lock();
        value ++;
    } finally {
        lock.unlock();
        lock.unlock();
    }
}

synchronized关键字也是可重入锁。基于monitor机制实现。而ReentrantLock基于AQS实现。

  1. 公平锁与非公平锁
  1. 公平锁:一个新的线程来了之后,看到有很多线程处于阻塞状态,即AQS中的阻塞队列不为空,自己排到队伍末尾,进入阻塞状态。
  2. 公平锁:一个线程来了之后,不管有没有其他线程处于阻塞状态,直接抢锁,抢到了则执行,否则进入阻塞队列。
  1. Concurrent包下的Lock接口:Lock接口定义了锁的基本方法,所有的锁的实现类都实现了Lock接口。
public interface Lock {
	// 获取锁,获取到则返回,否则处于阻塞状态直到获取到锁。且不可响应中断
    void lock();
    // 获取锁,获取到则返回,否则处于阻塞状态直到获取到锁。但是可以响应中断
    void lockInterruptibly() throws InterruptedException;
    // 尝试获取锁,无论能否获取到锁,都返回。获取到则返回true,获取不到则返回false
    boolean tryLock();
    // 尝试获取锁,获取到则直接返回true,获取不到则阻塞,并等待指定的time时间。
    // 若到达指定的time时间后,仍然无法获取到锁则返回false
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    // 释放当前持有的锁
    void unlock();
    // 返回一个Condition对象,通过condition可以达到唤醒指定线程的目的。稍后会细讲
    Condition newCondition();
}

锁实现的基本原则(AQS)

  1. 基本上Concurrent包中的Lock的实现类,都只是一个代理类,内部有Sync对象完成真正的同步操作,而Sync的父类AbstractQueuedSynchronizer经常被称为队列同步器(AQS)。如ReentrantLock的类继承关系如下:
    ReentrantLock继承图
  2. AQS实现的核心要素:
  1. 需要一个state变量,标记该锁的状态。state变量至少有两个值:0,1。对state变量的操作,要确保线程安全,也就是要用到CAS。
  2. 需要记录当前是哪个线程持有锁,以此做到锁的可重入性。
  3. 需要底层支持对一个线程进行阻塞或者唤醒操作。LockSupport类的park()方法和unpark()方法,通过底层Unsafe类的park()方法和unpark()方法,很好的支持了线程的阻塞与唤醒。
  • park():在当前线程调用park(),当前线程t就会进入阻塞状态。
  • unpark(Thread t): 在另外一个线程中,调用unpark(t),传入一个阻塞的线程,就可以唤醒阻塞在park()方法的线程。从而实现一个线程对另一个线程的“精准唤醒”。
  1. 需要有一个队列维护所有阻塞的线程。这个队列也必须是线程安全的无锁队列,因此也需要用到CAS。AQS中利用双向链表+CAS实现的一个线程安全的阻塞队列
public class LockSupport {
	......
	// 在当前线程调用park(),当前线程t就会进入阻塞状态。
	public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
    //调用unpark(t),传入一个阻塞的线程,就可以唤醒阻塞在park()方法的线程t。
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
    ......
}

本人水平有限,因此给大家推荐一篇介绍AQS的经典好文,与大家共勉。有兴趣的童鞋可以关注:https://www.javadoop.com/post/AbstractQueuedSynchronizer


©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页