Lock体系

Lock简介

锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源。在Lock接口出现之前,java程序主要是靠synchronized关键字实现锁功能的,而JDK5之后,并发包中增加了lock接口,它提供了与synchronized一样的锁功能。虽然它失去了像synchronize关键字隐式加锁解锁的便捷性,但是却拥有了锁获取和释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。通常使用显示使用lock的形式如下:

Lock lock = new ReentrantLock();
try {
	lock.lock();
	// 以下代码只有一个线程可以运行
	...
}
finally{
	lock.unlock();
}

需要注意的是synchronized同步块执行完成或者遇到异常是锁会自动释放,而lock必须调用unlock()方法释放锁,因此在finally块中释放锁

Lock常用API

lock体系拥有可中断的获取锁以及超时获取锁以及共享锁等内建锁不具备的特性。

 void lock(); // 获取锁 
 void lockInterruptibly() throws InterruptedException; // 获取锁的过程能够响应中断 
 boolean tryLock(); // 非阻塞式响应中断能立即返回,获取锁返回true反之为false 
 boolean tryLock(long time,TimeUnit unit);// 超时获取锁,在超时内或未中断的情况下能获取锁 
 Condition newCondition(); // 获取与lock绑定的等待通知组件,当前线程必须先获得了锁才能等待,等待会释放锁,再次获取到锁才能从等待中返回。

基 本 上 所 有 的 方 法 的 实 现 实 际 上 都 是 调 用 了 其 静 态 内 存 类 Sync 中 的 方 法 , 而 Sync 类 继 承 了 AbstractQueuedSynchronizer(AQS--简称同步器)

AQS

  • 同步器是用来构建锁以及其他同步组件的基础框架,它的实现主要是依赖一个int状态变量以及通过一个 FIFO队列共同构成同步队列。
  • 子类必须重写AQS的protected修饰的用来改变同步状态的方法,其他方法主要是实现了排队与阻塞机制。int状态的更新使用getState()setState()以及compareAndSetState()
  • 子类推荐使用静态内部类来继承AQS实现自己的同步语义。同步器即支持独占锁,也支持共享锁。

同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。可以这样理解二者的关系:锁是面向使用者,它定义了使用者与锁交互的接口,隐藏了实现细节;同步器是面向锁的实现者,它简化了锁的实现方式,屏蔽了同步状态的管理,线程的排队,等待和唤醒等底层操作。锁和同步器很好的隔 离了使用者和实现者所需关注的领域。

AQS的模板方法设计模式

AQS的设计是使用模板方法设计模式,它将一些与状态相关的核心方法开放给子类重写,而后AQS会使用子类重写的关于状态的方法进行线程的排队、阻塞以及唤醒等操作。

  1. 同步组件(这里不仅仅指锁,还包括CountDownLatch等)的实现依赖于同步器AQS,在同步组件实现中,使用 AQS的方式被推荐定义继承AQS的静态内存类;
  2. AQS采用模板方法进行设计,AQS的protected修饰的方法需要由继承AQS的子类进行重写实现,当调用AQS的子类的方法时就会调用被重写的方法;
  3. AQS负责同步状态的管理,线程的排队,等待和唤醒这些底层操作,而Lock等同步组件主要专注于实现同步语 义;
  4. 在重写AQS的方式时,使用AQS提供的getState(),setState(),compareAndSetState()方法进行修改同步状态。


AQS提供的模板方法可以分为3类:

  1. 独占式获取与释放同步状态;
  2. 共享式获取与释放同步状态;
  3. 查询同步队列中等待线程情况;

自己实现一个简易Lock锁

package www.bit.java;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

class Metux implements Lock{
    private Syns syns = new Syns();
    static class Syns extends AbstractQueuedSynchronizer{
        @Override
        protected boolean tryAcquire(int arg) {
            if(arg != 1){
                throw new RuntimeException("arg参数不为1");
            }
            if(compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            if(getState() == 1){
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        /*
        当前同步器是否在独占模式下被线程占用
        表示是否被当前线程所独占
         */
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        Condition newCondition(){
            return new ConditionObject();
        }
    }
    @Override
    public void lock() {
        syns.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        syns.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return syns.tryAcquire(1);
    }

    /*
    超时获取锁,在超时内或未中断的情况下能获取锁
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {
        syns.release(1);
    }

    /*
    获取与lock绑定的等待通知组件,
    当前线程必须先获得了锁才能等待,等待会释放锁,再次获取到锁才能从等待中返回。
     */
    @Override
    public Condition newCondition() {
        return syns.newCondition();
    }
}
public class TestDemo {
    private static Metux metux = new Metux();
    public static void main(String[] args) {
        for(int i = 0;i < 10;i++){
            Thread thread = new Thread(() -> {
                metux.lock();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    metux.unlock();
                }
            });
            thread.start();
        }
    }
}


上面这个例子实现了独占锁的语义,在同一时刻只允许一个线程占有锁。我们在主线程中启动了10个线程,分别睡眠2s。从执行情况我们可以看出,当前Thread-1正在执行并且占有锁而其他线程需要等待获取锁。

按照推荐的方式,Mutex定义了一个继承AQS的静态内部类Sync,并且重写了AQS的tryAcquire等等方法,而对state的更新也是利用了setState(),getState(),compareAndSetState()这三个方法。在实现lock接口中的方法也只是调用了 AQS提供的模板方法(因为Sync继承AQS)。

从这个例子就可以很清楚的看出来,在同步组件的实现上主要是利用了AQS,而AQS“屏蔽”了同步状态的修改,线程排队等底层实现,通过AQS的模板方法可以很方便的给同步组件的实现者进行调用。而针对用户来说,只需要调用同步组件提供的方法来实现并发编程即可。

同时在新建一个同步组件时需要把握的两个关键点是:

  1. 实现同步组件时推荐定义继承AQS的静态内存类,并重写需要的protected修饰的方法;
  2. 同步组件语义的实现依赖于AQS的模板方法,而AQS模板方法又依赖于被AQS的子类所重写的方法。

总的来说,同步组件通过重写AQS的方法实现自己想要表达的同步语义,而AQS只需要同步组件表达的true和false 即可,AQS会针对true和false不同的情况做不同的处理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章