1. 程式人生 > >Java多執行緒學習筆記16之Lock的使用

Java多執行緒學習筆記16之Lock的使用

詳細程式碼見:github程式碼地址

 

本節內容:

1) Lock介面/ReentrantLock類/lock()/unlock()方法文件翻譯

    翻譯中包含了此重入鎖與synchronized方法及語句塊之間區別及優缺點

2) 使用ReentrantLock實現同步


第四章


Lock的使用

內容:
Java5中的Lock物件也能實現同步的效果,而且在使用上也更加方便。
本章主要掌握以下兩個知識點:
1) ReentrantLock的使用
2) ReentrantReadWriteLock類的使用

1.使用ReentrantLock類
在Java多執行緒中,可以使用synchronized(重量鎖)關鍵字來實現執行緒之間的同步互斥,但在
JDK1.5中新增加了ReentrantLock(輕量鎖)類也能達到同樣的效果,並且在擴充套件功能上也更加
強大,比如具有嗅探鎖定,多路分支通知等。在中等或更高負荷下,ReentrantLock
有更好的效能,並且擁有可輪詢和可定時的請求鎖等高階功能。
重入鎖ReentrantLock:

     也叫作遞迴鎖,顧名思義,就是支援重新進入的鎖,它表示該鎖能夠支援一個線
程對資源的重複加鎖,用於佔有鎖的執行緒再次獲取鎖的場景。同一執行緒中某個外層函式
獲得鎖之後,其內層程式碼再次獲取該鎖,形成遞迴呼叫,而不受影響。


(1) 文件翻譯(Lock介面和ReentrantLock類及lock()、unlock()方法)
翻譯中其實也介紹了重入鎖與synchronized加鎖的區別

ReentrantLock類
public class ReentrantLock
extends Object
implements Lock, Serializable 可以看到此類實現了Lock及Serializable序列化介面
A reentrant mutual exclusion Lock with the same basic behavior and semantics as 
the implicit monitor lock accessed using synchronized methods and statements,
but with extended capabilities.
A ReentrantLock is owned by the thread last successfully locking, but not yet 
unlocking it. A thread invoking 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 isHeldByCurrentThread(), and getHoldCount().
與使用同步方法和語句訪問的隱式監視器鎖相同的基本行為和語義的可重入互斥鎖。但具有可拓展
能力。ReentrantLock物件是由最後一次成功鎖定,但尚未解鎖它的執行緒擁有的.當鎖沒有被其他
執行緒擁有的時候,一個執行緒通過呼叫lock()方法來成功獲得鎖。當前鎖是否被別的執行緒鎖擁有
可以使用isHeldByCurrentThread()和getHoldCount()進行檢查。

The constructor for this class accepts an optional fairness parameter. When set 
true, under contention, locks favor granting access to the longest-waiting 
thread. Otherwise this lock does not guarantee any particular access order. 
Programs using fair locks accessed by many threads may display lower overall 
throughput (i.e., are slower; often much slower) than those using the default 
setting, but have smaller variances in times to obtain locks and guarantee lack 
of starvation. Note however, that fairness of locks does not guarantee fairness 
of thread scheduling. Thus, one of many threads using a fair lock may obtain it 
multiple times in succession while other active threads are not progressing and 
not currently holding the lock. Also note that the untimed tryLock() method does 
not honor the fairness setting. It will succeed if the lock is available even if 
other threads are waiting.
此類的建構函式接受可選的公平性引數。如果設定為true,在"爭用"情況下,鎖將更支援給最長等待
時間的執行緒。否則,此鎖不保證任任何特定的訪問順序。使用由多執行緒訪問的公平鎖的程式可能會顯示
較低的總體吞吐量(即,速度較慢;通常要慢的多),而不是使用預設的設定(即,預設情況下不指定
預設是非公平鎖),但有時會有較小的差異來獲取鎖並保證缺乏飢餓。但注意,公平鎖並不保證執行緒排程
的公平性。因此,多執行緒中使用公平鎖中的一個執行緒可以連續多次獲得這個鎖,而其他存活的執行緒沒有
進展,並且當前不持有鎖。另外要注意的是,tryLock()方法不尊重公平性的設定。如果鎖可用,即使
其他執行緒正在等待,它也會成功

It is recommended practice to always immediately follow a call to lock with a try
block, most typically in a before/after construction such as:
建議的做法總是立即用try-catch程式碼塊在構造之前之後加入,例如:

 
 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }
In addition to implementing the Lock interface, this class defines a number 
of public and protected methods for inspecting the state of the lock. Some 
of these methods are only useful for instrumentation and monitoring.
此類除了實現鎖介面,此類還定義了許多公有和受保護的方法來檢查鎖的狀態。其中一些方法
僅適用於檢測和監控。

Serialization of this class behaves in the same way as built-in locks: a 
deserialized lock is in the unlocked state, regardless of its state when serialized.
此類的序列化行為方式與內建鎖相同: 反序列化的鎖處於"未鎖定"的狀態,而不管其被序列化時的狀態。

This lock supports a maximum of 2147483647 recursive locks by the same 
thread. Attempts to exceed this limit result in Error throws from locking methods.
此鎖最多支援同一執行緒重入這麼多次,嘗試超過次限制將導致從鎖定方法引發錯誤。

Since:
1.5


Constructors:
ReentrantLock():
Creates an instance of ReentrantLock
建立一個重入鎖例項,預設是非公平鎖
ReentrantLock(boolean fair):
Creates an instance of ReentrantLock with the given fairness policy.
建立一個給定公平政策的重入鎖例項


lock():
public void lock​()
Acquires the lock.
Acquires the lock if it is not held by another thread and returns immediately, 
setting the lock hold count to one.
獲取鎖.
如果它沒有被另一個執行緒所持有,那麼獲得這個鎖,並且立即返回。將鎖計數器設定為1


If the current thread already holds the lock then the hold count is incremented
by one and the method returns immediately.
如果當前執行緒已經持有鎖,則鎖計數器加1,該方法立即返回。

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.
如果鎖由另一個執行緒持有,則當前執行緒將被禁用以執行緒排程,並且在獲取鎖之前處於休眠狀態,此時
鎖定保留計數設定為1.(其實是在阻塞佇列中)

unlock():
public void unlock​()
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 IllegalMonitorStateException is thrown.
嘗試釋放當前鎖
如果當前執行緒是此鎖的持有者,則保留計數器將-1.如果鎖計數器為0,則釋放鎖。如果當前執行緒不是此
鎖的持有者,則丟擲IllegalMonitorStateException異常。


Specified by:
unlock in interface Lock
Throws:
IllegalMonitorStateException - if the current thread does not hold this lock
丟擲IlleagalMonitorStateException - 如果當前執行緒並不持有鎖


Lock介面:
public interface Lock
Lock implementations provide more extensive locking operations than can be obtained 
using synchronized methods and statements. They allow more flexible structuring, 
may have quite different properties, and may support multiple associated Condition 
objects.
A lock is a tool for controlling access to a shared resource by multiple threads. 
Commonly, a lock provides exclusive access to a shared resource: only one thread 
at a time can acquire the lock and all access to the shared resource requires that 
the lock be acquired first. However, some locks may allow concurrent access to a 
shared resource, such as the read lock of a ReadWriteLock.
Lock實現提供比使用synchronized方法和語句塊更加廣泛的鎖操作。它們允許更靈活的結構,可能
具有完全不同的屬性,並且可能支援多個關聯的條件物件。
鎖是在多執行緒中用來控制對共享資源的訪問的一種工具。通常,鎖提供一種排它的對共享資源的獨佔訪問
(但是還有讀寫鎖,讀寫排它,其他不排斥);同一時間只有一個執行緒可以獲得這個鎖,並且對共享資源的
所有訪問都要求首先獲取鎖。但是,某些鎖可能允許併發訪問共享資源,如ReadWriteLock的讀鎖定。

The use of synchronized methods or statements provides access to the implicit 
monitor lock associated with every object, but forces all lock acquisition 
and release to occur in a block-structured way: when multiple locks are 
acquired they must be released in the opposite order, and all locks must 
be released in the same lexical scope in which they were acquired.
使用同步synchronized方法或語句塊可以訪問與每個物件關聯的隱式監視器鎖。但強制
所有鎖的捕獲和釋放以塊結構方式發生:在獲取多個鎖時,必須在相反的順序釋放。並且所有鎖
必須在其獲取的同一詞法範圍內釋放。

While the scoping mechanism for synchronized methods and statements makes 
it much easier to program with monitor locks, and helps avoid many common 
programming errors involving locks, there are occasions where you need to 
work with locks in a more flexible way. For example, some algorithms for 
traversing concurrently accessed data structures require the use of 
"hand-over-hand" or "chain locking": you acquire the lock of node A, 
then node B, then release A and acquire C, then release B and acquire 
D and so on. Implementations of the Lock interface enable the use of 
such techniques by allowing a lock to be acquired and released in different 
scopes, and allowing multiple locks to be acquired and released in any order.
雖然同步方法和同步語句塊的作用域機制(就是花括號)使使用監視器鎖進行程式設計變得更加容易,
並且有助於避免涉及鎖的許多常見程式設計錯誤,但有時需要使用更靈活的方式來處理鎖。例如,一些
遍歷併發訪問資料結構的演算法需要使用"手動"或"鏈鎖定": 獲取節點a的鎖,然後是節點B,然後釋放
a並獲取C,然後釋放B並且D等等。(但是synchronized難以實現)。鎖介面的實現通過允許在不同的
作用域中獲取和釋放鎖,並允許以任何順序獲取和釋放多個鎖,從而允許使用此類技術。


With this increased flexibility comes additional responsibility. The absence 
of block-structured locking removes the automatic release of locks that 
occurs with synchronized methods and statements. In most cases, the 
following idiom should be used:
隨著靈活性的增加,來了更多的責任。缺少塊結構鎖定移除了synchronized方法或語句塊會自動
獲得和釋放鎖的能力。在大多數情況下,應使用一下習語:

 
 Lock l = ...;
 l.lock();
 try {
   // access the resource protected by this lock
 } finally {
   l.unlock();
 }
When locking and unlocking occur in different scopes, care must be taken to 
ensure that all code that is executed while the lock is held is protected by 
try-finally or try-catch to ensure that the lock is released when necessary.
Lock implementations provide additional functionality over the use of synchronized 
methods and statements by providing a non-blocking attempt to acquire a lock
(tryLock()), an attempt to acquire the lock that can be interrupted 
(lockInterruptibly(), and an attempt to acquire the lock that can timeout 
(tryLock(long, TimeUnit)).
當鎖定和解鎖發生在不同的範圍內時,必須小心,以確保在鎖定時執行的所有程式碼都在try-catch
-finally受保護的,以確保在必要時釋放鎖。鎖實現通過提供在synchronized方法及語句塊
之上的非阻塞方法tryLock()方法來獲得鎖,試圖獲取鎖可以被中斷,並且可能超時tryLock(long,
TimeUnit)

A Lock class can also provide behavior and semantics that is quite different 
from that of the implicit monitor lock, such as guaranteed ordering, non-reentrant 
usage, or deadlock detection. If an implementation provides such specialized 
semantics then the implementation must document those semantics.
Lock類還可以提供與隱式監視器鎖完全不同的行為和語義。(如保證排序,不可重入用法或死鎖檢測)
如果實現提供了此專用語義,那麼實現必須記錄這些語義。

Note that Lock instances are just normal objects and can themselves be used as 
the target in a synchronized statement. Acquiring the monitor lock of a Lock
instance has no specified relationship with invoking any of the lock() methods 
of that instance. It is recommended that to avoid confusion you never use Lock 
instances in this way, except within their own implementation.
請注意,鎖例項只是普通物件,並且可以作為同步語句塊中的目標使用。獲取鎖例項的監視器與呼叫
該例項的任何lock()方法沒有指定的關係。建議避免混亂,你永遠不會這樣使用鎖例項,除非在它們
的實現中。

Except where noted, passing a null value for any parameter will result in a 
NullPointerException being thrown.
還有,為任何引數傳遞null將導致空指標異常。

Memory Synchronization
記憶體同步

All Lock implementations must enforce the same memory synchronization semantics 
as provided by the built-in monitor lock, as described in Chapter 17 of The 
Java™ Language Specification:
所有的鎖實現必須強制執行與內建監視器鎖提供的相同記憶體同步語義

下面就是將要實現的原則:
A successful lock operation has the same memory synchronization effects as a 
successful Lock action.
A successful unlock operation has the same memory synchronization effects as 
a successful Unlock action.
Unsuccessful locking and unlocking operations, and reentrant locking/unlocking 
operations, do not require any memory synchronization effects.
Implementation Considerations

The three forms of lock acquisition (interruptible, non-interruptible, and timed) 
may differ in their performance characteristics, ordering guarantees, or other 
implementation qualities. Further, the ability to interrupt the ongoing acquisition 
of a lock may not be available in a given Lock class. Consequently, an implementation 
is not required to define exactly the same guarantees or semantics for all three 
forms of lock acquisition, nor is it required to support interruption of an 
ongoing lock acquisition. An implementation is required to clearly document 
the semantics and guarantees provided by each of the locking methods. It must 
also obey the interruption semantics as defined in this interface, to the 
xtent that interruption of lock acquisition is supported: which is either totally
, or only on method entry.
三種形式的鎖獲取(可中斷、不可中斷和定時)可能不同於其效能特點,順序保證及其他實現質量。此外,在
給定的鎖類中可能無法使用中斷正在進行的鎖獲取的能力,因此,不需要實現對所有三種形式的鎖獲取完全
相同的保證或語義,也不需要支援正在進行的鎖獲取中斷。需要實現以清楚地記錄每個鎖定方法提供的語義
和保證。它還必須遵循此介面中定義的中斷語義, 優劣支援鎖獲取中斷: 這要麼完全, 或僅在方法項上。

As interruption generally implies cancellation, and checks for interruption are 
often infrequent, an implementation can favor responding to an interrupt over 
normal method return. This is true even if it can be shown that the interrupt 
occurred after another action may have unblocked the thread. An implementation 
should document this behavior.
由於中斷通常意味著取消,並且通常不頻繁地檢查中斷,因此實現可以支援通過尋常方法返回對
一箇中斷的相應。當中斷髮生在另一個操作阻塞執行緒也成立。


lock():
void lock​()
Acquires the lock.
If the lock is not available then the current thread becomes disabled for thread 
scheduling purposes and lies dormant until the lock has been acquired.

Implementation Considerations

A Lock implementation may be able to detect erroneous use of the lock, such as an 
invocation that would cause deadlock, and may throw an (unchecked) exception in 
such circumstances. The circumstances and the exception type must be documented 
by that Lock implementation.
一個鎖實現可能檢測到錯誤地使用鎖,如將導致死鎖的呼叫,並且這種情況下引發(未檢查)異常。該鎖
實現必須記錄環境和異常型別。


unlock():
void unlock​()
Releases the lock.
Implementation Considerations

A Lock implementation will usually impose restrictions on which thread can release 
a lock (typically only the holder of the lock can release it) and may throw an 
(unchecked) exception if the restriction is violated. Any restrictions and the 
exception type must be documented by that Lock implementation.
鎖實現通常會對那些執行緒可以釋放鎖施加限制(通常只有鎖的只有這可以釋放它),如果違反限制,則可能引發
(未檢查異常)。任何限制和異常型別都必須通過該鎖實現記錄。

 

(2) 使ReentrantLock實現同步
測試1

package chapter04.section01.thread_4_1_1.project_1_ReentrantLockTest;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyService {
	
	private Lock lock = new ReentrantLock();
	
	public void testMethod() {
		lock.lock();
		for(int i = 0; i < 5; i++) {
			System.out.println("ThreadName=" + Thread.currentThread().getName()
					+ (" " + (i + 1)));
		}
		lock.unlock();
	}
}


package chapter04.section01.thread_4_1_1.project_1_ReentrantLockTest;

public class MyThread extends Thread {
	private MyService service;
	
	public MyThread(MyService service) {
		super();
		this.service = service;
	}
	
	@Override
	public void run() {
		service.testMethod();
	}
}


package chapter04.section01.thread_4_1_1.project_1_ReentrantLockTest;

public class Run {
	
	public static void main(String[] args) {

		MyService service = new MyService();

		MyThread a1 = new MyThread(service);
		MyThread a2 = new MyThread(service);
		MyThread a3 = new MyThread(service);
		MyThread a4 = new MyThread(service);
		MyThread a5 = new MyThread(service);

		a1.start();
		a2.start();
		a3.start();
		a4.start();
		a5.start();
	}
}
/*
result:
ThreadName=Thread-3 1
ThreadName=Thread-3 2
ThreadName=Thread-3 3
ThreadName=Thread-3 4
ThreadName=Thread-3 5
ThreadName=Thread-0 1
ThreadName=Thread-0 2
ThreadName=Thread-0 3
ThreadName=Thread-0 4
ThreadName=Thread-0 5
ThreadName=Thread-1 1
ThreadName=Thread-1 2
ThreadName=Thread-1 3
ThreadName=Thread-1 4
ThreadName=Thread-1 5
ThreadName=Thread-4 1
ThreadName=Thread-4 2
ThreadName=Thread-4 3
ThreadName=Thread-4 4
ThreadName=Thread-4 5
ThreadName=Thread-2 1
ThreadName=Thread-2 2
ThreadName=Thread-2 3
ThreadName=Thread-2 4
ThreadName=Thread-2 5
*/

結果分析:
當前執行緒列印完畢之後將鎖進行釋放,其他執行緒才可以繼續列印。執行緒列印的資料是
分組列印,因為當前執行緒已經持有鎖,但執行緒之間列印的順序是隨機的。

測試2:

package chapter04.section01.thread_4_1_2.project_1_ConditionTestMoreMethod;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyService {

	private Lock lock = new ReentrantLock();

	public void methodA() {
		try {
			lock.lock();
			System.out.println("methodA begin ThreadName="
					+ Thread.currentThread().getName() + " time="
					+ System.currentTimeMillis());
			Thread.sleep(5000);
			System.out.println("methodA  end ThreadName="
					+ Thread.currentThread().getName() + " time="
					+ System.currentTimeMillis());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public void methodB() {
		try {
			lock.lock();
			System.out.println("methodB begin ThreadName="
					+ Thread.currentThread().getName() + " time="
					+ System.currentTimeMillis());
			Thread.sleep(5000);
			System.out.println("methodB  end ThreadName="
					+ Thread.currentThread().getName() + " time="
					+ System.currentTimeMillis());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

}


package chapter04.section01.thread_4_1_2.project_1_ConditionTestMoreMethod;

public class ThreadA extends Thread {

	private MyService service;

	public ThreadA(MyService service) {
		super();
		this.service = service;
	}

	@Override
	public void run() {
		service.methodA();
	}
}


package chapter04.section01.thread_4_1_2.project_1_ConditionTestMoreMethod;

public class ThreadAA extends Thread {

	private MyService service;

	public ThreadAA(MyService service) {
		super();
		this.service = service;
	}

	@Override
	public void run() {
		service.methodA();
	}
}


package chapter04.section01.thread_4_1_2.project_1_ConditionTestMoreMethod;


public class ThreadB extends Thread {

	private MyService service;

	public ThreadB(MyService service) {
		super();
		this.service = service;
	}

	@Override
	public void run() {
		service.methodB();
	}
}


package chapter04.section01.thread_4_1_2.project_1_ConditionTestMoreMethod;

public class ThreadBB extends Thread {

	private MyService service;

	public ThreadBB(MyService service) {
		super();
		this.service = service;
	}

	@Override
	public void run() {
		service.methodB();
	}
}


package chapter04.section01.thread_4_1_2.project_1_ConditionTestMoreMethod;

public class Run {

	public static void main(String[] args) throws InterruptedException {
		MyService service = new MyService();

		ThreadA a = new ThreadA(service);
		a.setName("A");
		a.start();
		ThreadAA aa = new ThreadAA(service);
		aa.setName("AA");
		aa.start();

		Thread.sleep(100);

		ThreadB b = new ThreadB(service);
		b.setName("B");
		b.start();
		
		ThreadBB bb = new ThreadBB(service);
		bb.setName("BB");
		bb.start();
	}
}
/*
result:
methodA begin ThreadName=A time=1540723593087
methodA  end ThreadName=A time=1540723598088
methodA begin ThreadName=AA time=1540723598088
methodA  end ThreadName=AA time=1540723603088
methodB begin ThreadName=B time=1540723603088
methodB  end ThreadName=B time=1540723608088
methodB begin ThreadName=BB time=1540723608088
methodB  end ThreadName=BB time=1540723613089
*/

結果分析:
呼叫lock.lock()程式碼的執行緒就持有了"物件監視器",其他執行緒只有等待鎖被釋放時再次爭搶。