7.并发编程-锁的探究

可重入锁

synchronized

ReentrantLock

可重入锁存在是为了解决死锁的问题。可重入锁的实现原理就是基于aqs里面的state计数状态来实现的。如果一个线程已经拿到锁,并且再次调用加锁的方法时候,会首先使用tryAcquire方法判断拿到锁的线程是否是当前线程,不是的话抛出异常,是的话原来的state+1。当线程用完锁释放时候,在释放锁方法tryRelease里面,会对state-1,直到state=0时候,下个线程才会来取锁。

跟踪源码state变化

首先新建一个类,写上下面方法,

可以想象下,如果你住别墅,别墅每次只能进出一个人去睡觉,首先是不是需要一把大门的钥匙和一把房间的钥匙,你才可以最终上传睡觉。两把钥匙缺一不可。
锁不可重入的话,就类比于你想睡觉,但是手上的锁要么是大门锁,要么是房间锁,如果你用过大门锁把他丢在门外(相当于锁的释放),那下一个人就可以拿到大门锁进入大门,如果是坏人,那你睡觉就不安全了。
锁可重入的话比如想保证你在睡觉的时候,别墅只有你一个人的话,那么最好的办法就是你把大门锁携带着去睡觉,让别人进不了大门。等第二天起来后,把大门锁和房间锁在归还等下个人来别墅睡觉。你可以把自己看成一个线程,首先拿到大门的锁,打开大门后,把大门关闭,不让别的线程进来,然后你再打开房间的锁,去睡觉。第二天起来把这两把锁归还给下一个线程。

public class LockTest {
	//非公平、公平锁
	private :noFairLock = new ReentrantLock(false);
	private ReentrantLock fairLock = new ReentrantLock(true);
	/**
     *
     * 非公平锁
     */

      public void testNoFairLock() {
        noFairLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "正在使用【非公平锁】");
            System.out.println(Thread.currentThread().getName() +"打开了家里的大门....");
            entryRoomsNoFair();
        } finally {
            noFairLock.unlock();
        }
    }

    public void entryRoomsNoFair() {
        noFairLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() +"打开大门后走进他的房间...");
            sleepNoFair();
        } finally {
            noFairLock.unlock();
        }
    }

    public void sleepNoFair() {
        noFairLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() +"进入房间后开始睡觉...");
        } finally {
            noFairLock.unlock();
        }
    }
	//线程进行抢锁行为
      public static void main(String[] args) throws ExecutionException, InterruptedException {
        LockTest lockTest = new LockTest();
        System.out.println("main线程开始-----");
       new Thread(()->{
                lockTest.testNoFairLock();
            }).start();
    }
}

上面代码如果锁是不可重入的话,那仅仅是打开家里大门,拿着大门的锁不释放,你是无法在拿到房间的锁。也就产生死锁的问题。下面断点跟进下state的变化。

Lock方法

ReentrantLock 源码205行

	//每个想要获取锁的线程都会执行该方法
   final void lock() {
   		//如果锁未被使用(state=0),通过cas进行state=1赋值操作,如果设置成功就已经拿到锁
        if (compareAndSetState(0, 1))
        		//蒋当前线程变成持有锁访问权限的线程
              setExclusiveOwnerThread(Thread.currentThread());
          else
          	//锁被占用的时候,调用acquire方法
              acquire(1);
     	 }
   }
acquire方法

AbstractQueuedSynchronizer源码1197行

 public final void acquire(int arg) {
 	//如果尝试获取锁失败并且成功蒋当前线程打包成节点并且加入等待队列中,中断这个线程
      if (!tryAcquire(arg) &&
           acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
           selfInterrupt();
   }
nonfairTryAcquire方法
   final boolean nonfairTryAcquire(int acquires) {
   			//获取当前线程
            final Thread current = Thread.currentThread();
            //获取当前的state
            int c = getState();
            //如果state=0,说明锁未被使用,cas尝试设置值,成功蒋当前线程设置成可以拿锁的权限
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //satte!=0说明锁已经被使用,如果当前拿锁的线程就是已经获取锁权限的线程。给state+=acquire的值,
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //以上条件都不符合,返回fasle,尝试获取锁失败,这时候会,将失败的线程打包成节点,加入队列中进行等待
            return false;
        }
release方法
//AQS里面的释放锁的方法,释放成功,则唤醒后面的几点(如果后面节点存在)
 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
tryRelease方法
	//尝试释放锁,如果释放锁线程不是锁拥有的线程抛出异常,
    protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
             //默认锁是不空闲的。除非拥有锁的线程,已经全部把锁释放结束,才认为锁是空闲的
            boolean free = false;
            if (c == 0) {
                free = true;
                //表示当前没有任何线程拥有锁的访问权限
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

addWaiter方法
 //很简单,就是指将未抢到锁的线程进行节点打包,返回节点
 private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
acquireQueued方法
//以独占不间断(自旋方式),将为获取到锁的线程打包成节点加入到队列中,进行等待。
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

图形化跟踪非公平锁的源码

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

公平锁与非公平锁本质区别

在这里插入图片描述

两者的本质区别在于公平锁多了个hasQueuedPredecessors判断,其具体含义是 :如果当前线程之前有一个排队线程,则为true ,如果当前线程位于队列的头部或队列为空,则为false。
这也就意味着所有在等待拿锁的线程都必须要进行排队拿锁,这中间存在线程的唤起和挂起的时间,是不可避免的。而非公平锁属于抢占式锁,谁先到谁先拿锁。看下图。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值