4.并发编程之Lock锁原理

并发编程之Lock锁原理

J.U.C

Lock存在于J.U.C(Java.util.Concurrent)下。里面提供了很多工具,例如:BlockingQueue、ConcurrentHashMap、线程池、CountDownLatch等。

Lock与Synchronized

锁是用来解决线程安全问题的,它与Synchronized的作用是完全一样的,只是实现方式不同。

ReentrantLock

ReentrantLock又叫重入锁,是一种互斥锁,即一个线程获取了同一把锁,后续在释放这把锁之前,获取这把锁不需要加锁,只要记录重入的次数,它的重入特性与Synchronized一致的。

ReentrantLock使用

看下面的代码,代码的结果预期值为1000,但是实际值往往会比1000要小,这是由于inc()方法的是一个非原子性操作导致的。

public class LockTest {
    private static int count = 0;
    public static void inc() {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        for(int i=0; i<1000; i++) {
            new Thread(()->LockTest.inc()).start();
        }
        Thread.sleep(3000);
        System.out.println("Result:"+count);
    }
}
Result:983

怎么解决这个原子性问题呢?

public class LockTest {
    static Lock lock = new ReentrantLock();
    private static int count = 0;
    public static void inc() {
        //抢占锁
        lock.lock();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        for(int i=0; i<1000; i++) {
            new Thread(()->LockTest.inc()).start();
        }
        Thread.sleep(3000);
        System.out.println("Result:"+count);
    }
}
Result:1000

ReentrantLock实现原理

锁的核心功能:

满足线程的互斥特性

同一个时刻,只允许一个线程进入到加锁的代码中。保证多线程环境下,线程的顺序访问。

锁的设计猜想

  • 一定会涉及到锁的抢占,需要一个标记来实现互斥。需要一个全局变量(0,1)
  • 抢占到了锁,不需要处理
  • 没抢占到锁,怎么处理
    • 需要等待(让处于排队中的线程,如果没有抢占到锁,则直接阻塞,释放CPU资源)
      • 如何让线程等待
        • wait/notify(线程通信机制,无法指定唤醒某个线程)
        • LockSupport.park/unpark(阻塞一个指定线程,唤醒一个指定线程)
        • Condition
    • 需要排队(允许有N个线程被阻塞,此时线程处于活跃状态)
      • 通过一个数据结构,把这N个排队的线程存储起来
  • 抢占到锁的释放过程,如何处理
    • LockSupport.unpark,唤醒一个处于队列中的指定线程
  • 锁抢占的公平性(是否允许插队)
    • 公平
    • 非公平

AbstractQueuedSynchronizer(AQS)

上述对于锁的猜想,实际上是在AQS中实现的,Lock本身没有做特殊处理,核心的机制都在AQS中实现。AQS里面提供了两种机制:

  • 共享锁
  • 互斥锁

ReentrantLock实现原理分析

在这里插入图片描述

Lock

ReetrantLock

公平锁和非公平锁

公平锁
final void lock() { 
	acquire(1); //抢占1把锁.
}
public final void acquire(int arg) { // AQS里面的方法
	if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    	selfInterrupt();
    }
protected final boolean tryAcquire(int acquires) {
	final Thread current = Thread.currentThread(); 
  	int c = getState();
  	if (c == 0) { //表示无锁状态
    	if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { 
        	//CAS(#Lock) -> 原子操作| 实现互斥的判断
			setExclusiveOwnerThread(current); //把获得锁的线程保存到 exclusiveOwnerThread中
        	return true;
   		}
	}
	//如果当前获得锁的线程和当前抢占锁的线程是同一个,表示重入 
  	else if (current == getExclusiveOwnerThread()) {
    	int nextc = c + acquires; //增加重入次数. 
   		if (nextc < 0)
			throw new Error("Maximum lock count exceeded"); 
    	setState(nextc); //保存state
		return true;
	}
  	return false;
}
非公平锁
final void lock() { 
	//不管当前AQS队列中是否有排队的情况,先去插队
	if (compareAndSetState(0, 1)) //返回false表示抢占锁失败
    	setExclusiveOwnerThread(Thread.currentThread());
  	else
		acquire(1);
}
public final void acquire(int arg) {  // AQS
  	if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    	selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
	return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
  	final Thread current = Thread.currentThread();
  	int c = getState();
  	if (c == 0) {
    	//hasQueuedPredecessors
  		if (compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
      		return true;
		} 
  	}
  	else if (current == getExclusiveOwnerThread()) {
		int nextc = c + acquires;
		if (nextc < 0) // overflow
			throw new Error("Maximum lock count exceeded");
    	setState(nextc);
    	return true;
  	}
  	return false;
}

加入队列并进行自旋等待

public final void acquire(int arg) {
  	if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
}

acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

  • addWaiter(Node.EXCLUSIVE) -> 添加一个互斥锁的节点
  • acquireQueued() -> 自旋锁和阻塞的操作
private Node addWaiter(Node mode) {
	//把当前线程封装成一个Node节点。
	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;
}
private Node enq(final Node node) {
	for (;;) {//自旋 
  		Node t = tail;
		if (t == null) { // Must initialize 
    	//初始化一个head节点
			if (compareAndSetHead(new Node()))
      			tail = head;
    	} else {
      		node.prev = t;
			if (compareAndSetTail(t, node)) {
    			t.next = node;
				return t; 
      		}
		} 
  	}
}
//node表示当前来抢占锁的线程,有可能是ThreadB、 ThreadC。。 
final boolean acquireQueued(final Node node, int arg) {
	boolean failed = true;
  	try {
    	boolean interrupted = false; 
    	for (;;) { //自旋
		//begin ->尝试去获得锁(如果是非公平锁)
			final Node p = node.predecessor();
			if (p == head && tryAcquire(arg)) { //如果返回true,则不需要等待,直接返回
        		setHead(node);
        		p.next = null; // help GC
        		failed = false;
        		return interrupted;
        	}
      		//end
			//否则,让线程去阻塞(park)
			if (shouldParkAfterFailedAcquire(p, node) && 
				parkAndCheckInterrupt()) //LockSupport.park
				interrupted = true;
    		}
  	} finally {
		if (failed)
    	cancelAcquire(node);
  }
}
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);
  }
}


//ThreadB、 ThreadC、ThreadD、ThreadE -> 都会阻塞在下面这个代码的位置. 
private final boolean parkAndCheckInterrupt() {
	LockSupport.park(this); //被唤醒. (interrupt()->)
	return Thread.interrupted(); //中断状态(是否因为中断被唤醒的.) 
}

unlock

public final boolean release(int arg) {
	if (tryRelease(arg)) {
		Node h = head; //得到当前AQS队列中的head节点。
		if (h != null && h.waitStatus != 0) //head节点不为空
    		unparkSuccessor(h); //
      	return true;
	}
  return false;
}
private void unparkSuccessor(Node node) {
	int ws = node.waitStatus; 
  	if (ws < 0) //表示可以唤醒状态
		compareAndSetWaitStatus(node, ws, 0); //恢复成0
  	Node s = node.next;
	if (s == null || s.waitStatus > 0) { //说明ThreadB这个线程可能已经被销毁,或 者出现异常...
		s = null;
		//从tail -> head进行遍历.
		for (Node t = tail; t != null && t != node; t = t.prev)
			if (t.waitStatus <= 0) //查找到小于等于0的节点 
        s = t;
  	}
  	if (s != null)
    	LockSupport.unpark(s.thread); //封装在Node中的被阻塞的线程。ThreadB、ThreadC。
}
  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值