2012-05-22 39 views
14

我需要一個不可重入的ReadWriteLock,因爲這個鎖可能會被獲取它的線程釋放。 (我意識到這一點時,我開始拋出:IllegalMonitorStateException間歇。)我可以使用不可重入的ReadWriteLock嗎?

我不知道,如果非重入是正確的術語。 ReentrantLock允許當前持有的線程鎖定以再次獲取它。我不想要這種行爲,因此我稱之爲「不可重入」。

上下文是我有一個使用線程池的套接字服務器。每個連接沒有一個線程。請求可能會被不同的線程處理。客戶端連接可能需要鎖定一個請求並在另一個請求中解鎖。由於請求可能由不同的線程處理,我需要能夠在不同的線程中鎖定和解鎖。

承擔這個問題,我需要留在這個配置和我真的需要鎖定和不同的要求,因此有可能不同的線程解鎖的緣故。

這是一個ReadWriteLock中,因爲我需要讓多個「讀者」或獨有的「作家」。

看起來這可能使用的AbstractQueuedSynchronizer寫,但我怕如果我把它寫我自己,我會讓一些微妙的錯誤。我可以找到使用AbstractQueuedSynchronizer但不是ReadWriteLock的各種示例。

我可以採取在OpenJDK的ReentrantReadWriteLock源,並嘗試刪除折返的一部分,但我又害怕我不會得到它完全正確。

我看着番石榴和Apache共享,但沒有找到合適的東西。 Apache Commons有RWLockManager,它可以做我需要的東西,但我不確定,看起來比我需要的更復雜。

回答

24

信號量允許不同的線程執行許可證的獲取和發佈。獨佔寫入相當於擁有所有的許可,因爲線程一直等待,直到所有的許可都被釋放,並且其他線程不能獲得額外的許可。

final int PERMITS = Integer.MAX_VALUE; 
Semaphore semaphore = new Semaphore(PERMITS); 

// read 
semaphore.acquire(1); 
try { ... } 
finally { 
    semaphore.release(1); 
} 

// write 
semaphore.acquire(PERMITS); 
try { ... } 
finally { 
    semaphore.release(PERMITS); 
} 
+0

謝謝,這聽起來像它可能會伎倆。閱讀Semaphore的文檔聽起來像我需要指定「公平」選項。 「此課程還提供了便捷的方法來一次獲得和發佈多個許可證。當這些方法被公平使用時,無限期推遲的風險會增加。」 –

+0

我看到Semaphore實現了(至少在OpenJDK中)AbstractQueuedSynchronizer –

+0

太棒了!我發現自己身處同一條船(需要一個多線程/共用節儉服務器後面的r/w鎖)。這個問題和答案正是我需要的!謝謝! – akagixxer

5

我不確定我是否理解這個問題。一個非重入ReadWriteLock是一個矛盾的國際海事組織在線程白話中給出"reentrant" has specific meaning這個詞。它必須可以從多個線程使用,否則它將無法工作。在我看到的Java 6 JDK中只有ReentrantReadWriteLock實現ReadWriteLock。當然,多次讀取可以同時鎖定ReadWriteLock,但在這種情況下使用「重入」這個詞很容易混淆。

你應該做的是擁有良好的鎖定和解鎖代碼模式。例如,總是使用try {} finally塊來確保您獲得每個解鎖。

ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 
Lock readLock = readWriteLock.readLock(); 
... 
readLock.lock(); 
try { 
    ... // do stuff with the data 
} finally { 
    readLock.unlock(); 
} 

的暗示,即一個線程將lock()和另一unlock()不是你應該使用ReadWriteLock的方式。那很不好的模式。

看起來像這可能使用的AbstractQueuedSynchronizer

寫入但這會從你現在有相同問題的困擾。如果你沒有適當地使用它,你仍然可以通過線程訪問鎖外的受保護的Collection

我開始IllegalMonitorStateException間歇

我不會動不動就責怪你的ReadWriteLock這一點。我懷疑你有一些代碼是而不是鎖定一個集合,或者你正在修改一個意外讀鎖的集合。

+3

很好的答案。如果你的鎖在另一個線程上被解鎖,你沒有正確應用它們。 – derekerdmann

+3

不可重入可能是錯誤的術語。 ReentrantLock允許當前持有的線程鎖定以再次獲取它。我不想要這種行爲,因此我稱它爲「不可重入」。 上下文是,我有一個套接字服務器使用多個線程池。每個連接沒有一個線程。請求可能會被不同的線程處理。客戶端連接可能需要鎖定一個請求並在另一個請求中解鎖。由於請求可能由不同的線程處理,我需要能夠在不同的線程中鎖定和解鎖。 –

+1

這似乎過於複雜@Andrew。你可能會考慮每個連接執行一個線程,除非你打算有10,00個同時連接。如果你必須保持你的當前配置,那麼我會鎖定一個客戶端事務。我不會持有讀鎖,然後讓下一個線程處理的下一個客戶端trxn將其解鎖。不知道更多關於您的程序,很難進一步幫助進一步。 – Gray

1

不完全確定你需要什麼,尤其是,爲什麼它應該是一個讀寫鎖,但是如果你有需要被許多線程處理的任務,並且你不希望它被同時處理/訪問,我實際上會使用一個ConcurrentMap(等)。

您可以從地圖中刪除該任務,或將其替換爲特殊的「鎖定對象」以指示其已鎖定。您可以將具有更新狀態的任務返回到地圖以讓另一個線程接管,或者您可以直接將任務傳遞給下一個線程,讓它將任務返回到地圖。

+0

所以你建議我使用類似ConcurrentMap的東西來實現我自己的ReadWriteLock?我不確定這會比使用AbstractQueuedSynchronizer更容易/更好。我必須實施等待和排隊。 –

+0

@AndrewMcKinlay:不,我暗示你可能不需要一個ReadWriteLock。如果你想要的只是使用多個線程來處理一些任務,而這些線程在任務上執行一些不同的工作,那麼我的建議就是這樣做的一種方式。一般來說,如果讀者數量多於作者,則使用ReadWriteLock來獲得性能提升。實際上,可以設想在我的建議實現中放置一個ReadWriteLock(而不是像我的答案中所描述的那樣鎖定對象),但這是一種優化,而不是根本性的。 –

+0

我需要/想要多個閱讀器/單個寫入器作爲ReadWriteLock支持。 –

1

我知道你已經接受了另一個答案。但我仍然認爲你會爲自己製造一場噩夢。最終,客戶將無法返回併發布這些許可證,您會開始疑惑爲什麼「作家」從不寫信。

如果我這樣做,我會做這樣的:

Client issues a request to start a transaction 
The initial request creates a task (Runnable/Callable) and places it in an Executor for execution 
The initial request also registers that task in a Map by transaction id 

Client issues the second request to close the transaction 
The close request finds the task by transaction id in a map 
The close request calls a method on the task to indicate that it should close (probably a signal on a Condition or if data needs to be passed, placing an object in a BlockingQueue) 

現在,交易任務會有這樣的代碼:

public void run() { 
    readWriteLock.readLock().lock(); 
    try { 
     //do stuff for initializing this transaction 
     if (condition.await(someDurationAsLong, someTimeUnit)({ 
      //do the rest of the transaction stuff 
     } else { 
      //do some other stuff to back out the transaction 
     } 
    } finally { 
     readWriteLock.readLock.unlock(); 
    } 
} 
+0

感謝您的關注,我感謝您的反饋。你的方法很有趣。但是,每個事務都需要一個線程,並且由於連接可能有多個事務,所以它比每個連接的線程數多。我考慮並採取措施來處理這類問題 - 如果連接和交易閒置太久,它們都會被跟蹤並超時。 –

+0

我正在使用的軟件是在100年代的安裝過程中生產的,我已經看到了你警告我的那種濫用,我深知它必須得到防禦。不要誤解我的意思,我不是新手,但我也遠非完美。我相信我的一些設計決策並不是最優的。如果你有興趣,可以在我的博客上看到更多關於我的信息 - http://thesoftwarelife.blogspot.ca –

+0

不要誤解我的意思,我不質疑你的技能或智力,我認爲你聽起來很有能力,並且實際上表達清楚如果它是一個巨大的系統,旨在處理大量的併發事務,那麼是的,JVM可能會被迫爲額外的內存創建所有線程的堆棧空間(假設你有一個無限的線程池)。使用有界線程池時,執行程序會限制併發事務的數量。這當然不會太常見,因爲有些服務將這些類型的事物作爲參數。綁定池並且RejectedExecutionException停止新事務。 –

0

他們似乎已經掉在了球這一個通過廢棄com.sun.corba.se.impl.orbutil.concurrent.Mutex;

我的意思是,在他的右腦中,誰認爲我們不需要非重入鎖。在這裏,我們浪費我們的時間爭論可重入的定義(可以稍微改變每個框架的btw意義)。是的,我想在同一個線程上嘗試鎖定是如此糟糕的事情?它不會因爲其他問題而死鎖。鎖定在同一個線程中的非重入鎖可能非常有用,可以防止用戶在GUI應用程序中快速重複按下相同按鈕時發生錯誤。在那裏,做到了這一點,QT又是對的......再次。