ReentrantLock的实现使用的就是 park + 自旋的方式 ,下面举个例子来了解下 park 和 unpark 方法
public static void main(String[] args) throws InterruptedException { log.debug("1"); Thread t1 = new Thread(()->{ log.debug("2"); LockSupport.park();//让当前线程阻塞 log.debug("4"); }); //告诉cpu t1 当前可调度;具体什么时候调度是由操作系统决定的 t1.start(); //让主线程睡眠2秒 TimeUnit.SECONDS.sleep(2); log.debug("3"); LockSupport.unpark(t1);//唤醒t1线程}
打印结果
锁,其实就是一个标识,当这个标识改变成了某个状态我们理解为获得锁
public class CustomerLock { // status = 1; 不是原子性的,这是一条java代码 编译后会分为三条指令 gets=0 set1cache=1 set2 // 执行set1时 只是在当前线程操作 将status改为1 此时只是在内存中操作,还未同步到主存当中。 // 就是说线程t1修改status值还未同步写入主存时 t2也执行了相同操作,此时t2拿到的status还是0 volatile int status = 0; //实例化这个类 ①为了调用cas方法 ②获取status变量的偏移量 private static Unsafe unsafe = null; //CustomerLock当中status变量的内存偏移量 private static long statusOffset; //获取 Unsafe对象 static { Field singleOneInstanceField = null; try{ singleOneInstanceField = Unsafe.class.getDeclaredField("theUnsafe"); singleOneInstanceField.setAccessible(true); unsafe = (Unsafe) singleOneInstanceField.get(null); statusOffset = unsafe.objectFieldOffset( com.zld.cloud.CustomerLock.class.getDeclaredField("status")); }catch (Exception e){ e.printStackTrace(); } } void lock() throws InterruptedException{ while (!compareAndSet(0,1)){ TimeUnit.SECONDS.sleep(5); } } void unLock(){ status = 0; } private boolean compareAndSet(int oldVal, int newVal) { return unsafe.compareAndSwapInt(this,statusOffset,0,1); }}
public static void main(String[] args) throws InterruptedException { CustomerLock customerLock = new CustomerLock(); Thread t1 = new Thread(()->{ try { customerLock.lock(); } catch (InterruptedException e) { throw new RuntimeException(e); } log.debug("1"); log.debug("1"); log.debug("1"); log.debug("1"); log.debug("1"); customerLock.unLock(); },"t1"); Thread t2 = new Thread(()->{ try { customerLock.lock(); } catch (InterruptedException e) { throw new RuntimeException(e); } log.debug("2"); log.debug("2"); log.debug("2"); log.debug("2"); log.debug("2"); customerLock.unLock(); },"t2"); t1.start(); t2.start();}
测试结果截图
public class ReentrantLock implements Lock, java.io.Serializable { private static final long serialVersionUID = 7373984872572414699L; /** Synchronizer providing all implementation mechanics */ private final Sync sync; ... }
abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; /** * Performs {@link Lock#lock}. The main reason for subclassing * is to allow fast path for nonfair version. */ abstract void lock(); /** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { 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; } 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; } protected final boolean isHeldExclusively() { // While we must in general read state before owner, // we don't need to do so to check if current thread is owner return getExclusiveOwnerThread() == Thread.currentThread(); } final ConditionObject newCondition() { return new ConditionObject(); } // Methods relayed from outer class final Thread getOwner() { return getState() == 0 ? null : getExclusiveOwnerThread(); } final int getHoldCount() { return isHeldExclusively() ? getState() : 0; } final boolean isLocked() { return getState() != 0; } /** * Reconstitutes the instance from a stream (that is, deserializes it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // reset to unlocked state }}
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); //标识加锁成功后需要改变的状态 }}
public final void acquire(int arg) { //尝试加锁 如果加锁失败则 调用 acquireQueued 方法加入队列排队 //加入队列后会立刻park,直到解锁调用unpark醒来判断自己是否被打断 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();}
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread();//拿到当前线程 int c = getState();//获取lock对象的锁状态 0为自由状态 1为上锁 大于1表示重入 if (c == 0) { // 如果锁是自由状态 //判断当前是否需要排队,如果不需要则进行cas操作尝试加锁 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current);//如果加锁成功,将当前线程设置为拥有锁的线程 return true; } } //锁不是自由状态 但当前线程是持锁线程 表示重入则 状态值 +1 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false;}
public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());}
这里会有不需要排队的两种情况
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();}
- first节点持有了锁(将自己改成head)未释放,curr节点(此时是first)尝试加锁失败仍需要排队- 其他线程抢占了锁,队列中有节点排队,curr(不是first,它前面还有其他node)跟着排队
private Node addWaiter(Node mode) { //将当前线程封装成node对象 Node node = new Node(Thread.currentThread(), mode); //将尾节点赋值给pred Node pred = tail; if (pred != null) { //如果队尾不为null 说明队列已初始化 node.prev = pred;//设置当前节点的上个节点是原队列的尾节点 if (compareAndSetTail(pred, node)) {//cas操作防止多线程加锁,确保当前节点入队时的原子操作 pred.next = node;//将当前线程node设置为原队列尾节点的下一个节点 return node;//返回当前线程节点对象 } } enq(node);//队列还没初始化 return node;//返回当前线程节点对象}
private Node enq(final Node node) { //死循环 for (;;) { Node t = tail;//每次循环队尾节点赋值给t if (t == null) { //第一次循环时队尾肯定为null //调用无参构造方法实例化node对象 属性都为null if (compareAndSetHead(new Node())) //此时队列中只有一个新的虚拟node 使队列(链表)成立设置头尾相等 tail = head; } else {//第二次循环t一定不为null node.prev = t; // 将当前线程节点对象 放到尾节点(第二次循环尾节点就是头节点)之后 if (compareAndSetTail(t, node)) {//将当前线程对象node入队 并设置为队尾 t.next = node;//维护好链表设置头节 原来队尾下一个节点尾当前线程节点 return t;//返回队尾节点,即终止循环 } } }}
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) {//死循环,走进这个方法node已经完成了入队 //获得当前线程node的上个node有两种情况,1、p为head节点。2、p不为head final Node p = node.predecessor(); //如果p是head,那么当前线程对象node 为first, 尝试加锁,其实就是想看下head节点释放锁了没 if (p == head && tryAcquire(arg)) { setHead(node);//原head释放了锁,加锁成功将自身设置为head p.next = null; //原head的下一节点置空 因为当前node成为head failed = false;//标识 当前node加锁成功时为false return interrupted;//返回默认值 } //这里分两种情况 1、上个节点不是head 2、上个节点是head但未释放锁 修改上个节点状态park if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())//自身park interrupted = true; } } finally { if (failed) cancelAcquire(node); }}
Synchronized 1.6 之前对任何情况的处理相差不多,效率比较低下 ---重量级锁 并发编程之父Doug Lea 写了ReentrantLock
ReentrantLock的性能比 synchronized 高
当锁是自由状态是 公平锁需要判断自己是否要排队,而非公平锁直接cas操作,当锁不是自由状态非公平锁和公平锁没有区别,非公平锁的效率比公平锁高
sychronized 只支持公平锁
有的人认为公平锁就是排队非公平锁是插队,这种说法是不完全正确的。 实际上非公平锁入队之后是无法插队的 只能在入队之前的两次抢锁过程种可以算插队。
留言与评论(共有 0 条评论) “” |