欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Lock接口及其实现

程序员文章站 2022-05-05 16:34:02
...

目录

目标

ReentrantLock的实现。

锁的本质

因为资源会产生争抢,会产生线程安全问题,所以提出规则,只有抢到锁,才能访问资源。锁的本质实质是添加了一个规则,怎么样才能访问到资源,获得锁,即获得了资源的访问的资格。
获取锁要去抢,和谁创建的没有关系。

Lock API

  1. Locks包 类的层次结构
    Lock接口及其实现
  2. Lock接口
    lock():直到拿到锁为止,不会一直去尝试,会存储在队列中,会使线程阻塞。
    tryLock:锁被占用则放弃,只会尝试一次。
    lockInterruptibly():执行th.Interrupter方法则会放弃,可以监听到中断操作。
    Lock接口及其实现
  3. Condition
    Condition要和Lock接口一起使用,和wait/notify很像。
    和wait/notify区别:wait/notify只有一个等待集合存储被挂起的线程,Condition可以获取多个实例,相应的等待集合也会有多个,更灵活。
    Lock接口及其实现
  4. 基本使用
public class Demo3_Condition {
    private static Lock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        Thread th = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("condition.await()");
                try {
                    //加锁才能调用,否则会异常,线程阻塞,进入waiting,和wait方法一样,会释放锁
                    condition.await();
                    System.out.println("here i am...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        });
        th.start();
        Thread.sleep(2000L);
        lock.lock();
        //唤醒也必须拿到锁,唤醒的是队列头部的线程
        condition.signalAll();
        lock.unlock();

    }

}

ReentrantLock

特点:互斥、可重入、灵活。
owner:存储当前被占用的线程。
count:被加锁的次数,实现可重入机制。
加锁的次数和解锁的次数要相同。
Lock接口及其实现

  1. ReentrantLock实现原理解析
    说明:ReentrantLock实现Lock接口。
ReentrantLock.tryLock();
  • 比较owner是否为当前线程的引用,如果是,则count+=1,即可重入;如果否,则通过CAS操作来抢锁,修改count的值,count+1,修改成功,则抢锁成功,将owner设置为该线程的引用。
    代码示例说明
//标记重入次数的count值,CAS操作,保证原子性
//AtomicInteger count = new AtomicInteger(0);
public boolean tryLock() {
        //判断count是否为0,若count!=0,说明锁被占用
        int ct = count.get();
        if (ct !=0 ){
            //判断锁是否被当前线程占用,若被当前线程占用,做重入操作,count+=1
            if (owner.get() == Thread.currentThread()){
                count.set(ct + 1);
                return true;
            }else{
                //若不是当前线程占用,互斥,抢锁失败,return false
                return false;
            }
        }else{
            //若count=0, 说明锁未被占用,通过CAS(0,1) 来抢锁
            if (count.compareAndSet(ct, ct +1)){
                //若抢锁成功,设置owner为当前线程的引用
                owner.set(Thread.currentThread());
                return true;
            }else{
                //CAS操作失败,说明情锁失败 返回false
                return false;
            }
        }
    }
ReentrantLock.lock();

使用tryLock尝试一次,失败则放到等待队列中,如果是队列头部则阻塞等待,如果是队列头部,则取出,继续尝试。
代码示例说明

//等待队列
//private LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
public void lock() {
        //尝试抢锁
        if (!tryLock()){
            //如果失败,进入等待队列
            waiters.offer(Thread.currentThread());
            //自旋
            for (;;){
                //判断是否是队列头部,如果是
                Thread head = waiters.peek();
                if (head == Thread.currentThread()){
                    //再次尝试抢锁
                    if (!tryLock()){
                        //若抢锁失败,挂起线程,继续等待
                        LockSupport.park();
                    }else{
                        //若成功,就出队列
                        waiters.poll();
                        return;
                    }
                }else{
                    //如果不是,就挂起线程
                    LockSupport.park();
                }
            }
        }
    }

解锁

ReentrantLock.tryUnlock();
public boolean tryLock() {
        //判断count是否为0,若count!=0,说明锁被占用
        int ct = count.get();
        if (ct !=0 ){
            //判断锁是否被当前线程占用,若被当前线程占用,做重入操作,count+=1
            if (owner.get() == Thread.currentThread()){
                count.set(ct + 1);
                return true;
            }else{
                //若不是当前线程占用,互斥,抢锁失败,return false
                return false;
            }
        }else{
            //若count=0, 说明锁未被占用,通过CAS(0,1) 来抢锁
            if (count.compareAndSet(ct, ct +1)){
                //若抢锁成功,设置owner为当前线程的引用
                owner.set(Thread.currentThread());
                return true;
            }else{
                //CAS操作失败,说明情锁失败 返回false
                return false;
            }
        }
    }
public void unlock() {
        if (tryUnlock()){
            Thread th = waiters.peek();
            if (th !=null){
                LockSupport.unpark(th);
            }
        }
    }

synchronized vs Lock

synchronized由JVM提供,Lock是java写的。
只有synchronized会使线程进入blocked状态,lock使用park/unpark机制,使线程进入的是waiting状态。
卡片机即傻瓜相机,直接可用;单反即可自动调节,灵活性高,会用非常强大。
Lock接口及其实现

读写锁

在多线程环境下,可以多个线程同时读,只有一个线程写,避免单个线程挨个执行,提高性能。
写锁被占用,读锁无法被获取。

  1. ReadWriteLock
    维护了一对锁,写锁和读锁不会同时出现(排他)。
    owner:写锁的引用,不会记录读的线程。
    writeCount:写锁的加锁次数,CAS操作修改该值,修改成功,即抢锁成功。
    readCount:被多少个线程获取,共享。
    waiters:等待队列。
    线程饿死:读太多,一直被读占用,写锁获取不到。