java lock可重入_Java源碼解析之可重入鎖ReentrantLock

本文基于jdk1.8進行分析。

ReentrantLock是一個可重入鎖,在ConcurrentHashMap中使用了ReentrantLock。

首先看一下源碼中對ReentrantLock的介紹。如下圖。ReentrantLock是一個可重入的排他鎖,它和synchronized的方法和代碼有著相同的行為和語義,但有更多的功能。ReentrantLock是被最后一個成功lock鎖并且還沒有unlock的線程擁有著。如果鎖沒有被別的線程擁有,那么一個線程調用lock方法,就會成功獲取鎖并返回。如果當前線程已經擁有該鎖,那么lock方法會立刻返回。這個可以通過isHeldByCurrentThread方法和getHoldCount方法進行驗證。除了這部分介紹外,類前面的javadoc文檔很長,就不在這里全部展開。隨著后面介紹源碼,會一一涉及到。

/**

* A reentrant mutual exclusion {@link Lock} with the same basic

* behavior and semantics as the implicit monitor lock accessed using

* {@code synchronized} methods and statements, but with extended

* capabilities.

*

A {@code ReentrantLock} is owned by the thread last

* successfully locking, but not yet unlocking it. A thread invoking

* {@code lock} will return, successfully acquiring the lock, when

* the lock is not owned by another thread. The method will return

* immediately if the current thread already owns the lock. This can

* be checked using methods {@link #isHeldByCurrentThread}, and {@link

* #getHoldCount}.

首先看一下成員變量,如下圖。ReentrantLock只有一個成員變量sync,即同步器,這個同步器提供所有的機制。Sync是AbstractQueuedSynchronizer的子類,同時,Sync有2個子類,NonfairSync和FairSync,分別是非公平鎖和公平鎖。Sync,NonfaireSync和FairSync的具體實現后面再講。

/** Synchronizer providing all implementation mechanics **/

private final Sync sync;

下面看一下構造函數。如下圖。可以看到,ReentrantLock默認是非公平鎖,它可以通過參數,指定初始化為公平鎖或非公平鎖。

/**

* Creates an instance of {@code ReentrantLock}.

* This is equivalent to using {@code ReentrantLock(false)}.

**/

public ReentrantLock() {

sync = new NonfairSync();

}

/**

* Creates an instance of {@code ReentrantLock} with the

* given fairness policy.

* @param fair {@code true} if this lock should use a fair ordering policy

**/

public ReentrantLock(boolean fair) {

sync = fair ? new FairSync() : new NonfairSync();

}

下面看一下ReentrantLock的主要方法。首先是lock方法。如下圖。lock方法的實現很簡單,就是調用Sync的lock方法。而Sync的lock方法是個抽象的,具體實現在NonfairSync和FairSync中。這里我們先不展開講,而是先讀一下lock方法的注釋,看看它的作用。lock方法的作用是獲取該鎖。分為3種情況。

1,如果鎖沒有被別的線程占有,那么當前線程就可以獲取到鎖并立刻返回,并把鎖計數設置為1。

2,如果當前線程已經占有該鎖了,那么就會把鎖計數加1,立刻返回。

3,如果鎖被另一個線程占有了,那么當前線程就無法再被線程調度,并且開始睡眠,直到獲取到鎖,在獲取到到鎖時,會把鎖計數設置為1。

lockInterruptibly方法與lock功能類似,但lockInterruptibly方法在等待的過程中,可以響應中斷。

/**

* Acquires the lock.

*

Acquires the lock if it is not held by another thread and returns

* immediately, setting the lock hold count to one.

*

If the current thread already holds the lock then the hold

* count is incremented by one and the method returns immediately.

*

If the lock is held by another thread then the

* current thread becomes disabled for thread scheduling

* purposes and lies dormant until the lock has been acquired,

* at which time the lock hold count is set to one.

**/

public void lock() {

sync.lock();

}

public void lockInterruptibly() throws InterruptedException {

sync.acquireInterruptibly(1);

}

下面,詳細看一下非公平鎖和公平鎖中對lock函數的實現。如下圖。下圖同時列出了公平鎖和非公平鎖中lock的實現邏輯。從注釋和代碼邏輯中,都可以看出,非公平鎖進行lock時,先嘗試立刻闖入(搶占),如果成功,則獲取到鎖,如果失敗,再執行通常的獲取鎖的行為,即acquire(1)。

/**

* 非公平鎖中的lock

* 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);

}

//公平鎖中的lock

final void lock() {

acquire(1);

}

那么,我們首先了解下,非公平鎖“嘗試立刻闖入”,究竟做了什么。稍后再繼續講解通常的獲取鎖的行為。下圖是立即闖入行為compareAndSetState(0, 1)的實現。從compareAndSetState函數的注釋中,可以知道,如果同步狀態值與期望值相等,那么就把它的值設置為updated值。否則同步狀態值與期望值不相等,則返回false。這個操作和volatile有著相同的內存語義,也就是說,這個操作對其他線程是可見的。compareAndSetState函數注釋里描述的功能,是通過unsafe.compareAndSwapInt方法實現的,而unsafe.compareAndSwapInt是一個native方法,是用c++實現的。那么繼續追問,c++底層是怎么實現的?C++底層是通過CAS指令來實現的。什么是CAS指令呢?來自維基百科的解釋是,CAS,比較和交換,Compare and Swap,是用用于實現多線程原子同步的指令。它將內存位置的內容和給定值比較,只有在相同的情況下,將該內存的值設置為新的給定值。這個操作是原子操作。那么繼續追問,CAS指令的原子性,是如何實現的呢?我們都知道指令時CPU來執行的,在多CPU系統中,內存是共享的,內存和多個cpu都掛在總線上,當一個CPU執行CAS指令時,它會先將總線LOCK位點設置為高電平。如果別的CPU也要執行CAS執行,它會發現總線LOCK位點已經是高電平了,則無法執行CAS執行。CPU通過LOCK保證了指令的原子執行。

現在來看一下非公平鎖的lock行為,compareAndSetState(0, 1),它期望鎖狀態為0,即沒有別的線程占用,并把新狀態設置為1,即標記為占用狀態。如果成功,則非公平鎖成功搶到鎖,之后setExclusiveOwnerThread,把自己設置為排他線程。非公平鎖這小子太壞了。如果搶占失敗,則執行與公平鎖相同的操作。

/**

* Atomically sets synchronization state to the given updated

* value if the current state value equals the expected value.

* This operation has memory semantics of a {@code volatile} read

* and write.

* @param expect the expected value

* @param update the new value

* @return {@code true} if successful. False return indicates that the actual

* value was not equal to the expected value.

**/

protected final boolean compareAndSetState(int expect, int update) {

// See below for intrinsics setup to support this

return unsafe.compareAndSwapInt(this, stateOffset, expect, update);

}

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

下面看一下公平鎖獲取鎖時的行為。如下圖。這部分的邏輯有些多,請閱讀代碼中的注釋進行理解。

/**

* 公平鎖的lock

**/

final void lock() {

acquire(1);

}

/**

* 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) {

/**

* acquire首先進行tryAcquire()操作。如果tryAcquire()成功時則獲取到鎖,即刻返回。

* 如果tryAcquire()false時,會執行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

* 操作。如果acquireQueued(addWaiter(Node.EXCLUSIVE), arg)true時,則當前線程中斷自己。

* 如果acquireQueued(addWaiter(Node.EXCLUSIVE), arg)false,則返回。

* 其中tryAcquire()操作在NonfairSync中和FairSync中實現又有所區別。

**/

if (!tryAcquire(arg) &&

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

selfInterrupt();

}

/**

* NonfairSync中的tryAcquire。

* @param acquires

* @return

**/

protected final boolean tryAcquire(int acquires) {

return nonfairTryAcquire(acquires);

}

/**

* 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) {

//c為0,表示目前沒有線程占用鎖。沒有線程占用鎖時,當前線程嘗試搶鎖,如果搶鎖成功,則返回true。

if (compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

}

}

else if (current == getExclusiveOwnerThread()) {

//c不等于0時表示鎖被線程占用。如果是當前線程占用了,則將鎖計數加上acquires,并返回true。

int nextc = c + acquires;

if (nextc < 0) // overflow

throw new Error("Maximum lock count exceeded");

setState(nextc);

return true;

}

//以上情況都不是時,返回false,表示非公平搶鎖失敗。

return false;

}

/**

* Fair version of tryAcquire. Don't grant access unless

* recursive call or no waiters or is first.

* 這個是公平版本的tryAcquire

**/

protected final boolean tryAcquire(int acquires) {

final Thread current = Thread.currentThread();

int c = getState();

if (c == 0) {

//c=0時表示鎖未被占用。這里是先判斷隊列中前面是否有別的線程。沒有別的線程時,才進行CAS操作。

//公平鎖之所以公平,正是因為這里。它發現鎖未被占用時,首先判斷等待隊列中是否有別的線程已經在等待了。

//而非公平鎖,發現鎖未被占用時,根本不管隊列中的排隊情況,上來就搶。

if (!hasQueuedPredecessors() &&

compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

}

}

else if (current == getExclusiveOwnerThread()) {

int nextc = c + acquires;

if (nextc < 0)

throw new Error("Maximum lock count exceeded");

setState(nextc);

return true;

}

return false;

}

/**

* Acquires in exclusive uninterruptible mode for thread already in

* queue. Used by condition wait methods as well as acquire.

* 當搶鎖失敗時,先執行addWaiter(Node.EXCLUSIVE),將當前線程加入等待隊列,再執行該方法。

* 該方法的作用是中斷當前線程,并進行檢查,知道當前線程是隊列中的第一個線程,并且搶鎖成功時,

* 該方法返回。

* @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;

}

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt())

interrupted = true;

}

} finally {

if (failed)

cancelAcquire(node);

}

}

接下來是tryLock方法。代碼如下。從注釋中我們可以理解到,只有當調用tryLock時鎖沒有被別的線程占用,tryLock才會獲取鎖。如果鎖沒有被另一個線程占用,那么就獲取鎖,并立刻返回true,并把鎖計數設置為1. 甚至在鎖被設置為公平排序的情況下,若果鎖可用,調用tryLock會立刻獲取鎖,而不管有沒有別的線程在等待鎖了。從這里我們總結出,不管可重入鎖是公平鎖還是非公平鎖,tryLock方法只會是非公平的。

/**

* Acquires the lock only if it is not held by another thread at the time

* of invocation.

*

Acquires the lock if it is not held by another thread and

* returns immediately with the value {@code true}, setting the

* lock hold count to one. Even when this lock has been set to use a

* fair ordering policy, a call to {@code tryLock()} will

* immediately acquire the lock if it is available, whether or not

* other threads are currently waiting for the lock.

* This "barging" behavior can be useful in certain

* circumstances, even though it breaks fairness. If you want to honor

* the fairness setting for this lock, then use

* {@link #tryLock(long, TimeUnit) tryLock(0, TimeUnit.SECONDS) }

* which is almost equivalent (it also detects interruption).

*

If the current thread already holds this lock then the hold

* count is incremented by one and the method returns {@code true}.

*

If the lock is held by another thread then this method will return

* immediately with the value {@code false}.

* @return {@code true} if the lock was free and was acquired by the

* current thread, or the lock was already held by the current

* thread; and {@code false} otherwise

**/

public boolean tryLock() {

return sync.nonfairTryAcquire(1);

}

public boolean tryLock(long timeout, TimeUnit unit)

throws InterruptedException {

return sync.tryAcquireNanos(1, unit.toNanos(timeout));

}

接下來是釋放鎖的方法unlock。代碼如下。unlock方式的實現,是以參數1來調用sync.release方法。而release方法是如何實現的呢?release方法首先會調用tryRelease方法,如果tryRelease成功,則喚醒后繼者線程。而tryRelease的實現過程十分清晰,首先獲取鎖狀態,鎖狀態減去參數(放鎖次數),得到新狀態。然后判斷持有鎖的線程是否為當前線程,如果不是當前線程,則拋出IllegalMonitorStateException。然后判斷,如果新狀態為0,說明放鎖成功,則把持有鎖的線程設置為null,并返回true。如果新狀態不為0,則返回false。從tryRelease的返回值來看,它返回的true或false,指的是否成功的釋放了該鎖。成功的釋放該鎖的意思是徹底釋放鎖,別的線程就可以獲取鎖了。這里要認識到,即便tryRelease返回false,它也只是說明了鎖沒有完全釋放,本次調用的這個釋放次數值,依然是釋放成功的。

/**

* Attempts to release this lock.

*

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

}

/**

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

if (h != null && h.waitStatus != 0)

unparkSuccessor(h);

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;

}

/**

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

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

}

接下來是newCondition方法。關于Condition這里不展開介紹,只是了解下該方法的作用。如下圖。該方法返回一個和這個鎖實例一起使用的Condition實例。返回的Condition實例支持和Object的監控方法例如wait-notify和notifyAll相同的用法。

1,如果沒有獲取鎖,調用Condition的await,signal,signalAll方法的任何一個時,會拋出IllegalMonitorStateException異常。

2,調用Condition的await方法時,鎖也會釋放,在await返回之前,鎖會被重新獲取,并且鎖計數會恢復到調用await方法時的值。

3,如果一個線程在等待的過程中被中斷了,那么等待就會結束,并拋出InterruptedException異常,線程的中斷標志位會被清理。

4,等待的線程以FIFO的順序被喚醒。

5,從await方法返回的線程們的獲取到鎖的順序,和線程最開始獲取鎖的順序相同,這是未指定情況下的默認實現。但是,公平鎖更鐘愛那些已經等待了最長時間的線程。

/**

* Returns a {@link Condition} instance for use with this

* {@link Lock} instance.

*

The returned {@link Condition} instance supports the same

* usages as do the {@link Object} monitor methods ({@link

* Object#wait() wait}, {@link Object#notify notify}, and {@link

* Object#notifyAll notifyAll}) when used with the built-in

* monitor lock.

*

*

If this lock is not held when any of the {@link Condition}

* {@linkplain Condition#await() waiting} or {@linkplain

* Condition#signal signalling} methods are called, then an {@link

* IllegalMonitorStateException} is thrown.

*

When the condition {@linkplain Condition#await() waiting}

* methods are called the lock is released and, before they

* return, the lock is reacquired and the lock hold count restored

* to what it was when the method was called.

*

If a thread is {@linkplain Thread#interrupt interrupted}

* while waiting then the wait will terminate, an {@link

* InterruptedException} will be thrown, and the thread's

* interrupted status will be cleared.

*

Waiting threads are signalled in FIFO order.

*

The ordering of lock reacquisition for threads returning

* from waiting methods is the same as for threads initially

* acquiring the lock, which is in the default case not specified,

* but for fair locks favors those threads that have been

* waiting the longest.

*

* @return the Condition object

**/

public Condition newCondition() {

return sync.newCondition();

}

可重入鎖還有一些其他的方法,這里就不一一介紹了。This is the end.

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。如果你想了解更多相關內容請查看下面相關鏈接

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/394043.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/394043.shtml
英文地址,請注明出處:http://en.pswp.cn/news/394043.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

matlab的qammod函數_基于-MATLAB下的16QAM仿真.doc

1.課程設計目的隨著現代通信技術的發展&#xff0c;特別是移動通信技術高速發展&#xff0c;頻帶利用率問題越來越被人們關注。在頻譜資源非常有限的今天&#xff0c;傳統通信系統的容量已經不能滿足當前用戶的要求。正交幅度調制QAM(Quadrature Amplitude Modulation)以其高頻…

POJ3264 【RMQ基礎題—ST-線段樹】

ST算法Code&#xff1a; //#include<bits/stdc.h> #include<cstdio> #include<math.h> #include<iostream> #include<queue> #include<algorithm> #include<string.h> using namespace std; typedef long long LL;const int N5e410;…

leetcode199. 二叉樹的右視圖(bfs)

給定一棵二叉樹&#xff0c;想象自己站在它的右側&#xff0c;按照從頂部到底部的順序&#xff0c;返回從右側所能看到的節點值。示例:輸入: [1,2,3,null,5,null,4] 輸出: [1, 3, 4] 解釋:1 <---/ \ 2 3 <---\ \5 4 <---解題思…

開發人員工作周報_如何增加找到開發人員工作的機會

開發人員工作周報In a recent job as a senior developer, I helped interview and hire many of my employer’s development team members. This is a brain dump of my advice based on those interviews.在最近擔任高級開發人員的工作中&#xff0c;我幫助面試和雇用了許多…

安全專家教你如何利用Uber系統漏洞無限制的免費乘坐?

本文講的是安全專家教你如何利用Uber系統漏洞無限制的免費乘坐&#xff1f;&#xff0c;近日&#xff0c;根據外媒報道&#xff0c;美國一名安全研究人員發現Uber上存在一處安全漏洞&#xff0c;允許發現這一漏洞的任何用戶在全球范圍內免費享受Uber乘車服務。據悉&#xff0c;…

flume介紹

flume 1.flume是什么 Flume:** Flume是Cloudera提供的一個高可用的&#xff0c;高可靠的&#xff0c;分布式的海量日志采集、傳輸、聚合的系統。** Flume僅僅運行在linux環境下** flume.apache.org(Documentation--Flume User Guide) Flume體系結構(Architecture)&#xff1a; …

threadx 信號量 應用_操作系統及ThreadX簡介.ppt

操作系統及ThreadX簡介操作系統及ThreadX簡介 軟件二部 2006.09 主要內容 多任務操作系統概述 ThreadX簡介 關于驅動的交流 操作系統概述 什么是操作系統 管理計算機的所有資源&#xff0c;并為應用程序提供服務的最重要的系統軟件 操作系統的目的 為用戶編程提供簡單的接口&am…

java中同步組件_Java并發編程(自定義同步組件)

并發包結構圖&#xff1a;編寫一個自定義同步組件來加深對同步器的理解業務要求&#xff1a;* 編寫一個自定義同步組件來加深對同步器的理解。* 設計一個同步工具&#xff1a;該工具在同一時刻&#xff0c;只允許至多兩個線程同時訪問&#xff0c;超過兩個線程的* 訪問將被阻塞…

maven學習資料

maven學習資料maven學習教程&#xff1a;What、How、Whyhttp://www.flyne.org/article/167Maven 那點事兒 https://my.oschina.net/huangyong/blog/194583項目管理工具&#xff1a;Maven教程http://www.flyne.org/article/884轉載于:https://www.cnblogs.com/zhao1949/p/634641…

leetcode127. 單詞接龍(bfs)

給定兩個單詞&#xff08;beginWord 和 endWord&#xff09;和一個字典&#xff0c;找到從 beginWord 到 endWord 的最短轉換序列的長度。轉換需遵循如下規則&#xff1a; 每次轉換只能改變一個字母。 轉換過程中的中間單詞必須是字典中的單詞。 說明: 如果不存在這樣的轉換序…

算法之旅 | 快速排序法

HTML5學堂-碼匠&#xff1a;前幾期“算法之旅”跟大家分享了冒泡排序法和選擇排序法&#xff0c;它們都屬于時間復雜度為O(n^2)的“慢”排序。今天跟大家分享多種排序算法里使用較廣泛&#xff0c;速度快的排序算法—— 快速排序法 [ 平均時間復雜度為O (n logn) ]。Tips 1&…

springmvd接收參數問題

問題描述&#xff1a; 好久不寫博客了&#xff0c;今天遇到一個問題&#xff0c;那就是post請求時&#xff0c;參數接收不到&#xff0c;當時我很納悶&#xff0c;看代碼&#xff1a; 就是這樣幾個參數&#xff0c;我使用postman請求時無法獲取參數&#xff1a; 報錯信息&#…

figma下載_如何在Figma中創建逼真的3D對象

figma下載by Gbolahan Taoheed Fawale通過Gbolahan Taoheed Fawale 如何在Figma中創建逼真的3D對象 (How to create realistic 3D objects in Figma) Prior to using Figma, I used Adobe Illustrator for most of my designs (like logos, mockups, illustrations, and so on…

OpenGL中的二維編程——從簡單的矩形開始

一、OpenGL的組成 圖元函數&#xff08;primitive function&#xff09;指定要生成屏幕圖像的圖元。包括兩種類型&#xff1a;可以在二維、三維或者四維空間進行定義的幾何圖元&#xff0c;如多邊形&#xff1b;離散實體&#xff1b;位圖。屬性函數&#xff08;attribute funct…

圓與平面的接觸面積_如果一個絕對的圓放在絕對的平面上,接觸面是不是無限小?...

這種問題其實并不難解答&#xff1a;如果你真的能找到一個絕對的圓還有一個絕對平的平面上&#xff0c;并且保證放上去之后圓和平面不會有任何變化&#xff0c;那么接觸面就可以是無限小&#xff01;如果不能&#xff0c;很抱歉&#xff0c;接觸面很顯然就不會是無限小&#xf…

leetocde1129. 顏色交替的最短路徑(bfs)

在一個有向圖中&#xff0c;節點分別標記為 0, 1, …, n-1。這個圖中的每條邊不是紅色就是藍色&#xff0c;且存在自環或平行邊。 red_edges 中的每一個 [i, j] 對表示從節點 i 到節點 j 的紅色有向邊。類似地&#xff0c;blue_edges 中的每一個 [i, j] 對表示從節點 i 到節點…

第38天:運算符、字符串對象常用方法

一、運算符 一元操作符 &#xff0c; --&#xff0c; &#xff0c; - 5 -6 邏輯操作符 !&#xff0c; &&&#xff0c; || 基本運算符 , -, *, /, % 關系操作符 >, <, >, <, , , !, ! 賦值 判斷 全等 條件操作符 &#xff08;三…

Redux Todos Example

此項目模板是使用Create React App構建的&#xff0c;它提供了一種簡單的方法來啟動React項目而無需構建配置。 使用Create-React-App構建的項目包括對ES6語法的支持&#xff0c;以及幾種非官方/尚未最終形式的Javascript語法 先看效果 這個例子可以幫助你深入理解在 Redux 中 …

有效電子郵件地址大全_如何優雅有效地處理介紹電子郵件

有效電子郵件地址大全by DJ Chung由DJ Chung 如何優雅有效地處理介紹電子郵件 (How to handle intro emails gracefully and effectively) 您想幫個忙時不想忘恩負義... (You don’t want to sound ungrateful when asking for a favor…) Let me tell you the story that ins…

notability錄音定位_Notability的一些使用技巧?

作為使用了一年Notability的考研狗 今天也來回答回答這個問題&#xff0c;希望可以給考研的同學一點點幫助。這個軟件的優點估計大家都知道&#xff0c;我在這里就不多說了。好吧&#xff0c;還有一個原因是我比較懶&#xff01;好了不多說廢話了&#xff0c;等會你們要打我了本…