java多线程的一些整理(切合源码和底层的实现)

多线程实现

网上有说四种有说三种的。其实我觉得应该算是三种。一种为继承Thread来实现,一种为实现Runnable接口,最后一种为实现Callable接口。
实现Runnable接口多用于匿名内部类,实现Callable接口是需要线程执行结束有个返回结果的。

多线程在JVM中的状态

新建(New),即我们new了一个线程。
就绪(Runnable),我们调用了线程的start方法,此时线程进入就绪池
运行(Running),这时我们的线程获取到了锁,也就是我们正在占用cpu处理逻辑。
阻塞(Blocked),线程释放了锁,此时锁对象交给了别的线程,cpu相同的交给了别人。阻塞分为三种。
一种是等待阻塞,这种阻塞是因为线程调用了await方法,调用后线程进入等待池中等待被唤醒,被唤醒时进入到就绪池
一种是同步阻塞,比如遇到Synchronized关键字,线程没有获取锁时,就会被放到就绪池中。
一种是其他阻塞,当我们使用了sleep和join方法时,或者IO操作时,这时也会阻塞,但是这个阻塞同上两种阻塞不一样。如果是Sleep是不会释放掉锁的,但是会释放CPU。join看下方详解。
死亡(Dead),当线程运行完或者抛出异常就会死亡,即运行完成。
在多线程中常出现的方法:Object.await(),Object.notify(),Object.notifyAll(),Thread.sleep(),Thread.join(),Thread.yield()
Object.await():将当前线程加入到等待池中,释放对象头中为本线程的对象的锁。可以设置一个等待时间,如果超过一定等待时间还未被唤醒就会抛出异常。**await只会释放当前获取到的锁,不是所有的锁。**例如一个线程按顺序获取到A,B,C三个对象的锁,那么await只会释放C的锁,AB还会一直持有。一个await和join死锁的实例。

public class JoinDemo {
	public static void main(String[] args) {
		PublicSource publicSource = new PublicSource();
		Thread threadA = new DemoThread(publicSource);
		//main先获取到publicSource的锁再启动threadA。
		synchronized (publicSource) {
			threadA.start();
			try { 
				//main当前获取了publicSource的锁,等待threadA的锁。threadA去尝试获取publicSource,但是这个锁被main获取,所以就导致了死锁。
				threadA.join();
			} catch (InterruptedException e) { 
				e.printStackTrace();
			}
		}
	}
}

class DemoThread extends Thread{
	PublicSource publicSource;
	
	public DemoThread(PublicSource publicSource) {
		this.publicSource = publicSource;
	}
	
	@Override
	public void run() {
		synchronized (publicSource) {
			publicSource.test();
		}
	}
}

class PublicSource{
	public synchronized void test() {
		System.out.println("hi");
	}
}

Object.notify():随机唤醒等待池中一个线程进入到就绪池
Object.notifyAll():唤醒等待池中所有线程进入到就绪池
Thread.sleep():同Object.await不同的是不会释放锁,在到一定时间后,继续运行。相同点在于都会空出CPU。
Thread.join():例如一个ThreadB和main线程,在main线程运行中调用ThreadB.join(),那么main线程需要等待ThreaB运行完成后才能继续运行。在join底层还是使用的是Object的await()方法,那么在main调用ThreadB的join方法,那么相当于main调用了自己的await方法,丢掉对象头的线程id为本线程的对象的锁。但是在join方法,join由synchronized修饰,那么main线程持有了ThreadB对象的锁。所以在join内的await方法被调用时,main释放的就是ThreadB的锁,同时main等待也是ThreadB的锁。在ThreadB运行结束后会在JVM的Thread.cpp中执行唤醒等待ThreaB对象的锁的线程。

// 位于/hotspot/src/share/vm/runtime/thread.cpp中
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
    // ...

    // Notify waiters on thread object. This has to be done after exit() is called
    // on the thread (if the thread is the last thread in a daemon ThreadGroup the
    // group should have the destroyed bit set before waiters are notified).
    // 有一个贼不起眼的一行代码,就是这行
    ensure_join(this);

    // ...
}


static void ensure_join(JavaThread* thread) {
    // We do not need to grap the Threads_lock, since we are operating on ourself.
    Handle threadObj(thread, thread->threadObj());
    assert(threadObj.not_null(), "java thread object must exist");
    ObjectLocker lock(threadObj, thread);
    // Ignore pending exception (ThreadDeath), since we are exiting anyway
    thread->clear_pending_exception();
    // Thread is exiting. So set thread_status field in  java.lang.Thread class to TERMINATED.
    java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
    // Clear the native thread instance - this makes isAlive return false and allows the join()
    // to complete once we've done the notify_all below
    java_lang_Thread::set_thread(threadObj(), NULL);

    // 同志们看到了没,别的不用看,就看这一句
    // thread就是当前线程,是啥?就是刚才例子中说的t1线程啊。
    lock.notify_all(thread);

    // Ignore pending exception (ThreadDeath), since we are exiting anyway
    thread->clear_pending_exception();
}

所有的就绪池和等待池都是相对于某个被锁的对象。不同的锁对象的等待池和就绪池都是单独存在的lock.notify_all(thread);其中这个thread就是被争锁的对象。
Thread.yield():暂停本线程,使线程进入到
就绪池
中,但是不释放锁。yield的作用是让本线程让出cpu,让cpu去调度其他线程。但是很有可能本线程在让出cpu后再次被cpu调度。
参考:
https://blog.csdn.net/qq_41701956/article/details/84861232 锁的状态
https://www.jianshu.com/p/4322fbe767c4 Thread的join如何锁定和解锁

Synchronized锁

Synchronized是java的一个关键字,作用为实现线程同步。Synchronized可以标记在方法上,可以作为一个语句块使用。
1.标记在静态方法上,锁对象为当前类对象。
2.标记在成员方法上,即不是静态方法。锁对象为类的实例。
3.如果为语句块,那么锁的对象为**()**内的对象。
如何标记一个对象的锁状态呢,在我们的对象头中(保存在方法区,1.7在永久代,1.8在元数据)。对象头保存了GC和锁信息。java对象头如下所示。
以下参考的为:https://www.jianshu.com/p/3d38cba67f8b

|-------------------------------------------------------|--------------------|
|                  Mark Word (32 bits)                  |       State        |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|-------------------------------------------------------|--------------------|
|  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|-------------------------------------------------------|--------------------|
|               ptr_to_lock_record:30          | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
|               ptr_to_heavyweight_monitor:30  | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
|                                              | lock:2 |    Marked for GC   |
|-------------------------------------------------------|--------------------|

这部分主要用来存储对象自身的运行时数据,如hashcode、gc分代年龄等。mark word的位长度为JVM的一个Word大小,也就是说32位JVM的Mark word为32位,64位JVM为64位。为了让一个字大小存储更多的信息,JVM将字的最低两个位设置为标记位。
|biased_lock|lock|状态|

  • | :-: | :-: | :-:
    |0|01|无锁|
    |1|01|偏向锁|
    |0|00|轻量级锁|
    |0|10|重量级锁|
    |0|11|GC标记|
    biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
    age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
    identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程Monitor中。
    thread:持有偏向锁的线程ID。
    epoch:偏向时间戳。
    ptr_to_lock_record:指向栈中锁记录的指针。
    ptr_to_heavyweight_monitor:指向管程Monitor的指针。
    Sychronized在1.5和1.6进行了两次大优化。在1.6的时候加入了锁膨胀,锁的等级从小到大为偏向锁->轻量级锁->重量级锁。
    偏向锁:锁通过cas来修改锁对象的对象头的线程ID,标记对象的锁被该线程持有。当锁对象没有其他线程来竞争时,同一个线程来获取锁时不用再CAS修改锁对象的对象头的线程ID。当有其他线程来争抢这个锁时,此时锁升级为轻量级锁。
    轻量级锁:获取锁的每个线程都通过CAS来设置锁对象的对象头为自己,以这样的方式来获取锁。当CAS的次数超过一定次数时,说明该锁争抢激烈,锁升级为重量级锁。
    重量级锁:重量级锁在创建时,同时会在堆中创建monitor 对象(叫ObjectMonitor,简称monitor),并将 Mark Word 指向该 monitor 对象。monitor 中有 cxq(ContentionList),EntryList ,WaitSet,owner,count。其中cxq ,EntryList ,WaitSet都是由ObjectWaiter的链表结构,owner指向持有锁的线程。monitor锁在堆中结构如下图所示。
    在这里插入图片描述
    参考:https://github.com/farmerjohngit/myblog/issues/15#
    waitset就是调用await等被放入等待池的线程。Ready Thread为被唤醒去获取锁的线程。
    如果一个线程获取到到该对象的锁(monitor的owner指向自己),那么count+1,并且因为synchronized为可重入锁,该对象的锁可以被同个线程重复获取,所以count可以一直加。如果获取到该对象锁的线程调用await等方法或者运行结束释放锁时,count-1。
void ATTR ObjectMonitor::enter(TRAPS) {
   
  Thread * const Self = THREAD ;
  void * cur ;
  // owner为null代表无锁状态,如果能CAS设置成功,则当前线程直接获得锁
  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  if (cur == NULL) {
     ...
     return ;
  }
  // 如果是重入的情况
  if (cur == Self) {
     // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions ++ ;
     return ;
  }
  // 当前线程是之前持有轻量级锁的线程。由轻量级锁膨胀且第一次调用enter方法,那cur是指向Lock Record的指针
  if (Self->is_lock_owned ((address)cur)) {
    assert (_recursions == 0, "internal state error");
    // 重入计数重置为1
    _recursions = 1 ;
    // 设置owner字段为当前线程(之前owner是指向Lock Record的指针)
    _owner = Self ;
    OwnerIsThread = 1 ;
    return ;
  }

  ...

  // 在调用系统的同步操作之前,先尝试自旋获得锁
  if (Knob_SpinEarly && TrySpin (Self) > 0) {
     ...
     //自旋的过程中获得了锁,则直接返回
     Self->_Stalled = 0 ;
     return ;
  }

  ...

  { 
    ...

    for (;;) {
      jt->set_suspend_equivalent();
      // 在该方法中调用系统同步操作
      EnterI (THREAD) ;
      ...
    }
    Self->set_current_pending_monitor(NULL);
    
  }

  ...

}
void ATTR ObjectMonitor::EnterI (TRAPS) {
    Thread * Self = THREAD ;
    ...
    // 尝试获得锁
    if (TryLock (Self) > 0) {
        ...
        return ;
    }

    DeferredInitialize () ;
 
	// 自旋
    if (TrySpin (Self) > 0) {
        ...
        return ;
    }
    
    ...
	
    // 将线程封装成node节点中
    ObjectWaiter node(Self) ;
    Self->_ParkEvent->reset() ;
    node._prev   = (ObjectWaiter *) 0xBAD ;
    node.TState  = ObjectWaiter::TS_CXQ ;

    // 将node节点插入到_cxq队列的头部,cxq是一个单向链表
    ObjectWaiter * nxt ;
    for (;;) {
        node._next = nxt = _cxq ;
        if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;

        // CAS失败的话 再尝试获得锁,这样可以降低插入到_cxq队列的频率
        if (TryLock (Self) > 0) {
            ...
            return ;
        }
    }

	// SyncFlags默认为0,如果没有其他等待的线程,则将_Responsible设置为自己
    if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
        Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
    }


    TEVENT (Inflated enter - Contention) ;
    int nWakeups = 0 ;
    int RecheckInterval = 1 ;

    for (;;) {

        if (TryLock (Self) > 0) break ;
        assert (_owner != Self, "invariant") ;

        ...

        // park self
        if (_Responsible == Self || (SyncFlags & 1)) {
            // 当前线程是_Responsible时,调用的是带时间参数的park
            TEVENT (Inflated enter - park TIMED) ;
            Self->_ParkEvent->park ((jlong) RecheckInterval) ;
            // Increase the RecheckInterval, but clamp the value.
            RecheckInterval *= 8 ;
            if (RecheckInterval > 1000) RecheckInterval = 1000 ;
        } else {
            //否则直接调用park挂起当前线程
            TEVENT (Inflated enter - park UNTIMED) ;
            Self->_ParkEvent->park() ;
        }

        if (TryLock(Self) > 0) break ;

        ...
        
        if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;

       	...
        // 在释放锁时,_succ会被设置为EntryList或_cxq中的一个线程
        if (_succ == Self) _succ = NULL ;

        // Invariant: after clearing _succ a thread *must* retry _owner before parking.
        OrderAccess::fence() ;
    }

   // 走到这里说明已经获得锁了

    assert (_owner == Self      , "invariant") ;
    assert (object() != NULL    , "invariant") ;
  
	// 将当前线程的node从cxq或EntryList中移除
    UnlinkAfterAcquire (Self, &node) ;
    if (_succ == Self) _succ = NULL ;
	if (_Responsible == Self) {
        _Responsible = NULL ;
        OrderAccess::fence();
    }
    ...
    return ;
}

monitor在实现上和AQS很相近,都有一个等待获取线程的队列。在获取锁的时候就从队列中挑选一个出来去获取锁。并且因为都是队列,那么有个获取锁的时候,多是在队列头部的对象获取到锁,并且在获取的时候都是通过一定次数的自旋去获取。但是他们两个在队列头部的对象有点儿不一样。在后面会对ReentrantLock和Synchronized进行一些对比。

ReentrantLock锁

ReentrantLock作为JUC(java包)里面最常被问到的对象之一。在JUC这个包中,其核心实现为AQS(Abstract Queued Synchronizer),在AQS这个类中(AbstractQueuedSynchronizer.java)定义了一个双向链表和一个int型的state,代码如下所示:

    /**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;

    /**
     * The synchronization state.
     */
    private volatile int state;

这个双向链表的节点中,包含了一些属性,如当前线程是共享还是独享的,当前线程是否放弃竞争锁,是否需要唤醒下一个线程来争取锁等。代码如下所示:

static final class Node {
    
    // 表明节点在共享模式下等待的标记
    static final Node SHARED = new Node();
    // 表明节点在独占模式下等待的标记
    static final Node EXCLUSIVE = null;

    // 表征等待线程已取消的
    static final int CANCELLED =  1;
    // 表征需要唤醒后续线程
    static final int SIGNAL    = -1;
    // 表征线程正在等待触发条件(condition)
    static final int CONDITION = -2;
    // 表征下一个acquireShared应无条件传播
    static final int PROPAGATE = -3;

    /**	 waitStatus的取值如下
     *   SIGNAL: 当前节点释放state或者取消后,将通知后续节点竞争state。
     *   CANCELLED: 线程因timeout和interrupt而放弃竞争state,当前节点将与state彻底拜拜
     *   CONDITION: 表征当前节点处于条件队列中,它将不能用作同步队列节点,直到其waitStatus被重置为0
     *   PROPAGATE: 表征下一个acquireShared应无条件传播
     *   0: None of the above
     */
    volatile int waitStatus;
    
    // 前继节点
    volatile Node prev;
    // 后继节点
    volatile Node next;
    // 持有的线程
    volatile Thread thread;
    // 链接下一个等待条件触发的节点
    Node nextWaiter;

    // 返回节点是否处于Shared状态下
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    // 返回前继节点
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
    
    // Shared模式下的Node构造函数
    Node() {  
    }

    // 用于addWaiter
    Node(Thread thread, Node mode) {  
        this.nextWaiter = mode;
        this.thread = thread;
    }
    
    // 用于Condition
    Node(Thread thread, int waitStatus) {
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

lock

在我们使用ReentrantLock的lock时,会先调用其内部类NonfairSync的lock,在NonfairSync.lock()中调用AbstractQueuedSynchronizer.acquire(int arg)(acquire(1))方法,在这个方法中会调用tryAcquire(int arg)方法,这个方法在AQS这个类中是一个抽象方法,还是要看NonfairSync的tryAcquire(int arg)方法,在NonfairSync.tryAcquire(int arg)调用同类的nonfairTryAcquire(int arg)方法。整个过程时序图如下所示:
在这里插入图片描述
以非公平锁为例,也就是nonfairsync为例,解读以下源码:
NonfairSync
lock(),ReentrantLock调用lock的时候就会先回去一次锁,如果获取不到在acquire去获取锁。

 	/**
    * Performs lock.  Try immediate barge, backing up to normal
    * acquire on failure.
    */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

在acquire中会调用tryAcquire(),如果tryAcquire()失败,那么就会执行acquireQueued将线程放到等待队列中。如果tryAcquire失败,并且acquireQuueued也失败,那么直接中断线程。
AbstractQueuedSynchronizer

 /**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

在tryAcquire又回到了NonfairSync

 protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

又到了nonfairTryAcquire这个方法,继续深入。

/**
 * 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) {//如果是0,代表没有线程占有锁,会再次获取锁。
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //判断当前获取到锁的线程是不是本线程,如果是,那么就不用再获取了,state+1,因为acquires就是前面传过来的1
    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;
}

如果在tryAcquire获取到锁,那么就返回true,在AbstractQueuedSynchronizer的acquire就不再走下去。如果为false,那么就要尝试将线程放到等待队列中(addWaiter(Node.EXCLUSIVE),独占模式),并切搁置本线程。
回到AbstractQueuedSynchronizer

	/**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    private Node addWaiter(Node mode) {
    	//新建一个node将当前线程放进去,如果为独享,mode为null,如果为共享,mode为一个new 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) { //tail不为空的情况,说明队列中存在节点数据
            node.prev = pred;  //将当前线程的Node的prev节点指向tail,先制定前节点
            if (compareAndSetTail(pred, node)) {//通过cas更新tail的对象,即更新为最新插入的对象
                pred.next = node;//cas成功,把旧的tail的next指针指向新的tail
                return node;
            }
        }
        enq(node);//在node == null情况下,将node添加到同步队列中
        return node;
    }	
	
	/**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize需要被初始化
                if (compareAndSetHead(new Node()))
                    tail = head;//初始化双向链表
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {//将新节点设置为tail(尾节点)
                    t.next = node;
                    return t;
                }
            }
        }
    }

	/**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    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;
                }
                // 如果前驱节点不是头节点,或者上面的获取锁操作失败,会走到这里
	            // 通过 shouldParkAfterFailedAcquire 方法我们可以知道:
	            // 当前线程会把前驱线程的 waitStatus 值设置为 -1,代表唤醒
	            // 当前驱节点获得锁并释放后,会根据这个 waitStatus 去判断是否需要唤醒后继线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    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;
}

private final boolean parkAndCheckInterrupt() {
	// 将当前线程挂起
    LockSupport.park(this);
    // 返回当前线程的中断标识
    return Thread.interrupted();
}

//以上参考自:https://blog.csdn.net/phaeton_lai/article/details/105177938

以上详细分析了lock流程,在非公平锁的情况下,新加入的线程会一直去尝试获取锁,而不是老老实实的放入等待队列。而且获取锁时都是采用的cas的方式去修改state的状态。

unlock

现在查看如何unlock;
ReentrantLock.unlock()->AbstractQueuedSynchronizer.release(1)->ReentrantLock.sync.tryRelease()->AbstractQueuedSynchronizer->unparkSuccessor(Node h);
以下为时序图:
在这里插入图片描述
首先调用的为unlock方法,代码如下所示:

	/**
     * Attempts to release this lock.
     *
     * <p>If the current thread is the holder of this lock then the hold
     * count is decremented.  If the hold count is now zero then the lock
     * is released.  If the current thread is not the holder of this
     * lock then {@link IllegalMonitorStateException} is thrown.
     *
     * @throws IllegalMonitorStateException if the current thread does not
     *         hold this lock
     */
    public void unlock() {
        sync.release(1);
    }

release方法分两步,第一步先释放锁,即设置state为0,然后判断是否需要唤醒下一节点和唤醒下一节点。如下所示:

 	/**
     * Releases in exclusive mode.  Implemented by unblocking one or
     * more threads if {@link #tryRelease} returns true.
     * This method can be used to implement method {@link Lock#unlock}.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryRelease} but is otherwise uninterpreted and
     *        can represent anything you like.
     * @return the value returned from {@link #tryRelease}
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            // 如果代表锁 owner 的头节点存在,并且 waitStatus 不为初始值,
        	// 唤醒后继节点
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease(一般进来参数都是1),计算重入了几次,判断当前节点的线程是否是获取到锁的线程。最后修改state为0。


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

unparkSuccessor在所有锁都退出来后才会执行,在这个方法主要就是唤醒其他节点。

 /**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
    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;
        //如果这个节点waitStatus为小于0,即有含义的,那么就给他设置为0,。
        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;
        //如果本节点的下一个节点为空或者下一个节点的waitStatus大于0(需要被取消)
        if (s == null || s.waitStatus > 0) {
            s = null;
            // 从尾节点向前遍历,找到最后一个需要被唤醒的节点,
		    // 即队列中的第一个没有被取消的节点,然后将其唤醒
		    // 这里有一个疑问,为什么要从后往前进行遍历?
		    // addWaiter() 和 enq() 两个方法中,节点入队的时候,
		    // 先执行的都是 node.prev = pred; 然后再通过 CAS 将当前节点设置成尾节点,
		    // 设置成功后再将前驱节点的后继节点设置为当前节点。
		    // 因此如果从前往后遍历,(当前置节点的后置没有设置为本节点)可能会出现断链的情况
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

一起上内容参考自https://blog.csdn.net/phaeton_lai/article/details/105177938

Synchronized和ReentrantLock的区别

  1. Synchronized由jvm实现,是一个关键字,可以标识在方法上和方法块;ReentrantLock由jdk实现,只有方法形式。
  2. Synchronized非公平,ReentrantLock可公平可不公平。
  3. Synchronized可以有个膨胀的过程,在前期适用CAS,在后期重量级锁使用monitor对象,减少了CPU的占用。ReentrantLock使用了大量的CAS机制,如果在等待时间长的锁获取上,非常消耗CPU资源。
  4. Synchronized不可以中断在等待锁的进程,而ReentrantLock可以使用trylock来获取锁,如果超时,那么可以执行其他后面的代码,避免死锁。
  5. Synchronized的新来的线程放到等待队列的最前面或者和面,唤醒时先唤醒最前面的线程(直接取第一个)。ReentrantLock新来的队列放到最后面,唤醒时先唤醒最前面的线程(从后向前遍历)。
  6. Synchronized由jvm自动管理锁的加载和释放,ReentrantLock需要自己手动设置和解锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值