synchronizde & ReentrantLock

1. 为什么要加锁

2. ReentrantLock 和 synchronized的区别?

  1) synchronized是关键字,属于jvm层面,  ReentrantLock是一个类,属性API层面

  2) 底层实现不同 synchronized 靠的是monitor对象, ReentrantLock 靠的是 AQS

  3) ReentrantLock 可以与多个Condition搭配使用

  4) ReentrantLock 可以实现公平锁  可中断锁, synchronized不可以.  (可中断锁 通过 lockInterruptibly 方法来实现!!)

    5) ReentrantLock 可以知道自己有没有成功获取到锁 (tryLock),synchronized 不可以

  6) ReentrantLock要结合try catch finally 使用, 确保获取所有,即便后面代码出现异常,也能够释放锁

ReentrantLock的底层实现 !!!


要理解 ReentrantLock, 需要先理解两个概念。state 和 waitstate  .

  state 表示加锁的次数,每进入一次锁,state加1,释放掉1次锁,state减1,state为0,表示此时没有锁占领资源

(Semaphore的 state与这里的state的含义完全不一样, Semaphore的state 表示的是还能运行几个线程!!!!!)

 

     可重入锁就是靠这个state来实现的。 state 为0时,没有线程来占领该锁,state>0时,判断拥有锁的线程是否是当前线程。是,state的值就加1.

  waiteState 是Node结点的状态,唤醒下一个结点就会用到这个waitState,并且每个结点的前一个结点的等待状态都要设置成signal状态。否则无法唤醒这个线程。  

ReentranLock 类 实现类 Lock接口. 让我们先康康Lock接口有哪些东西.

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
}

 

其中有四个方法是加锁用的,一个方法是解锁用的,先一定要明确一个问题,那就是锁的作用到底是什么? 一定要清晰,锁是用来改变线程的状态的,当线程执行到这里的时候,通过判断能够获取到锁,来决定线程继续向下执行,还是将线程挂起来。自选锁,他是通过让线程在不停的自旋,会消耗CPU,需要注意,CAS本身不会自旋,需要结合循环才能实现自旋.

1.  先看lock方法

  公平锁的

final void lock() {
    acquire(1);
}

  非公平锁

final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

  可见唯一的不同就是,非公平锁上来,先进行一个CAS操作,先比较state是否是0,state为0,表示当前没人占用资源,如果为0 ,则成功获取到锁,并将state设为1,然后设置当前线程为锁的持有线程。

  后面都会执行acquire(1) 函数,让我们康康。

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

  1)可以看到,先执行了一个tryAcquire(arg) 方法,康康    (就是通过这种方式实现的可重复锁! 通过state和 exclusiveOwnerThread (当前拥有锁的线程)。

        ( 如果让我们设计一个可重入锁,那么也是用这种方式!! 一个state记录锁的状态,一个exclusiveOwnerThread记录锁的拥有者 )

protected final boolean tryAcquire(int acquires) { 
       // 获取当前线程
final Thread current = Thread.currentThread();
       // 获取state 表示的是当前加锁的次数 
int c = getState();
       // 如果没有加锁
if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } }
       // 如果当前线程就是锁的拥有者,那么将state的值加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; }

  2)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; }

   入队操作   可以看到这里其实就是进行了自旋操作,在并发的情况下 ,必须要保证同步,这里就采用了自旋+CAS的方式

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
        // 如果队列没有建立,创建的新的Node结点,作为头节点 (新创建的队列的头尾结点是同一个)
if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head;
          // 如果队列已经建立,则将结点插入队尾 }
else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }

  acquireQueued方法

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
        // 获得 node的前一个结点
final Node p = node.predecessor();
        // 如果前面的结点是head结点 那么再次尝试获取锁 tryAcquire 就尝试一次 成功就返回true,失败返回false
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); } }

  shouldParkAfterFailedAcquire 

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 设置结点的等待状态   确保当前结点的前一个结点的 waitState (等待状态) 是signal ,只有signal才会返回true, 在unlock那里会用到
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

  parkAndCheckInterrupt 方法

 private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

  就是在这里将线程挂起的吗? LockSupport.park的作用。

  public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

 

   再来看一下unlock方法

 public void unlock() {
        sync.release(1);
    }

  

   public final boolean release(int arg) {
    // 如果减1后,state的值为0
if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }

  tryRelease(1), 将state的状态减1

    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); }
        // 重新设置 state的值 setState(c);
return free; }

  

private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
    // 唤醒下一个线程?? LockSupport.unpark(s.thread); }

  LockSupport.unpark 方法。

  public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

底层就是是如何挂起和唤醒线程的?  靠的是LockSupport.park 和 LockSupport.unpark 方法吗??  

 是的,线程的挂起和唤醒靠的就是这两个方法。作用类似于wait 和 notify

 

再来看看tryLock()

  可以看到这个方法是由返回值的,因此我们可以知道是否成功获取了锁

public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

 

 在lock中,其实也有用到这个方法,就是先判断是否已经加锁,如果没有就让当前线程持有锁,然后state+1, 返回true, 继续后面代码的运行,如果state不为0,就去判断锁的持有者是否是当前线程,如果是,state+1, 返回true, 继续代码的运行,如果不是,返回false. 这时候就看自己的代码如何通过这个返回值来决定后序代码的运行了。显然,这里获取锁即使失败了,也不会阻塞当前线程。

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;
        }

 

再来看一下,带时间的tryLcok, 在上面的基础上加入了时间。

public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

 

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }

 

   private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

 

再来看看, 可中断锁

 LockInterruptibly

public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

 

    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())         // 就是用这种方式实现的可中断!! 也就是线程通过 判断线程的可中断的状态,来抛异常。来实现可中断!!!!
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

  Thread.interrupt的代码如下, 即判断当前线程的Interrupt的标记是不是true. 也就是是否执行了interrupt  方法

public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

 

  如果执行的可中断锁,那么线程不会阻塞?? 一直在这里循环?? 不是,执行 parkAndCheckInterrupt方法后,当前线程就会阻塞,待线程被唤醒后,才会接着执行下面的代码。

    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();     /// 就是这里可中断 如果线程的中腹案状态为true,那么就会直接抛异常!!!
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

  那么是怎么中断线程的等待的呢? 如果线程已经被阻塞,就不会执行任何代码了啊, 那如何感知到此时线程的中断状态呢。 为什么lock方法和synchronized不能被中断呢?  不能因为是下面这个代码吧,这个代码是线程的阻塞状态被打断后,才执行的抛出异常,与底层的阻塞状态的被打断没有关系。

【就是,线程在阻塞状态中,才能执行的中断?????】

if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
}

 

线程在阻塞状态下,是能够被打断的,哇,怎么实现的?莫名其妙的感觉。

用synchronized加锁,获取到锁后,执行wait方法,使线程阻塞,然后在另外一个线程中,将t1线程的中断状态置为true, 然后就会抛出中断异常了,执行sleep方法也会抛出异常。这是为什么呢???

public class TestS {
    static Object object=new Object();

    synchronized void fun(){
        try {
            System.out.println("wait10s 开始了----");
            this.wait(10000);
          //  Thread.sleep(10000);
            System.out.println("wait10s 结束了》》》》》");
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("被打断了");
        }
    }
    public static void main(String[] args) {
        TestS testS=new TestS();
        Thread t1=new Thread(()->{
            System.out.println("t1开始执行");
            testS.fun();
        },"t1");
        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("准备打断");
        t1.interrupt();
        System.out.println("主线程over");
    }
}

 

 

验证,对于synchronized在获取锁的时候不会发生阻塞。

public class TestS {
                static Object object=new Object();

                synchronized void fun(){
                    try {
                        System.out.println(Thread.currentThread().getName()+"wait10s 开始了----");
                        //  this.wait(10000);
                        Thread.sleep(10000);
                        System.out.println(Thread.currentThread().getName()+"wait10s 结束了》》》》》");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        System.out.println(Thread.currentThread().getName()+"被打断了");
                    }
                }
                public static void main(String[] args) {
                    TestS testS=new TestS();
                    Thread t1=new Thread(()->{
                        System.out.println("t1开始执行");
                        testS.fun();
                    },"t1");
                    Thread t2=new Thread(()->{
                        System.out.println("t2开始执行");
                        testS.fun();
                    },"t2");
                    t1.start();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    t2.start();
                    try {
                        Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("准备打断");
        t2.interrupt();
        System.out.println("主线程over");
    }
}

用Thread.sleep 进行阻塞

 

 用wait方法进行阻塞

 

 可见,用wait方法和sleep 方法进行阻塞,效果是不一样的,因为sleep方法,阻塞后,不会释放锁,而wait方法执行后,会释放锁,这时,t2线程就能够获取到锁,然后执行wait方法阻塞,当t2的中断状态变为true时,就会抛异常。  不过synchronized的中断是在获取到到了锁的时候才会中断,在等待获取锁的过程中 不会发生中断,这与ReentrantLock的可中断锁是不一样的。

那么,还是那个问题,为什么在阻塞状态的时候,中断状态为true,就会抛出异常,跳出阻塞状态呢??

 

synchronized 底层实现


1) 修饰方法 

修饰方法, 修饰静态方法, 锁定的是类资源, 修饰实例方法,锁定的是对象实例, 修饰方法,相当于给方法加入了 acc_synchronized标记. 

2) 修饰代码端

修饰代码段, 可以给类上锁, 也可以给实例上锁. 底层 加入了monitorenter 和 monitorexit, 其中monitorexit 有两个,一个是正常退出 , 一个是出现异常退出,用来保证即使出现了异常, 仍然会释放掉资源.

靠的都是底层的一个monitor对象来实现的。 (对象在内存中存储的时候,由三部分组成:对象头部、实例数据、对齐填充      对象头部就含有 Monitor对象的地址)

 

AQS的 selfInterrupt的作用是什么?自我中断?意义是什么呢?

 

interrupt方法仅仅是设置一个中断标记位吗?

 

可中断锁到底是怎么实现的?

  

 

posted @ 2020-07-04 18:17  你眼里的星辰  阅读(150)  评论(0)    收藏  举报