2013-02-21 147 views
2

我在採訪中被問及以下問題。多線程 - 共享數據

有一個對象被多個線程共享。該對象具有以下功能。如何確保不同的線程可以同時爲不同的參數x值執行函數?如果兩個線程正在執行相同的x值,則應阻止其中一個線程。

public void func(String x){ 
----- 
} 

「同步」的關鍵字不會在這種情況下工作,因爲它會確保只有一個線程可以同時執行。請讓我知道什麼將會成爲這個

+0

我的KISS感覺讓我告訴你只要使方法「同步」,並且在實際上成爲瓶頸時回到發燒友同步。 – 2013-02-21 05:42:31

+0

要點 - 「你的問題很蠢」很少是面試官想聽到的,但是! – 2013-02-21 05:46:51

回答

6

,想到的第一件事情解決的辦法是像

public void func(String x){ 
    synchronized (x.intern()) { 
     // Body here 
    } 
} 

這將表現就像描述;當然,這感覺就像一個討厭的黑客,因爲被同步的對象是公開的,其他代碼可能會因此而干擾鎖定。

+0

此解決方案的另一個問題是:它不適用於x的空值。 – 2013-02-21 05:55:17

+0

@EyalSchneider,好點 - 雖然根據完整的規範,拋出'NullPointerException'可能是對null參數的完美迴應。 – 2013-02-21 05:57:46

0

創建一個HashMap作爲成員變量。

private HashMap<String,Lock> lockMap = new HashMap<String,Lock>(); 
public void func(String x){ 
    if(lockMap.get(x) == null){ 
     lockMap.put(x,new ReentrantLock()); 
    } 
    lockMap.get(x).lock(); 
    ... 
    ... 
    lockMap.get(x).unlock(); 
} 
+2

當然,你需要使你的檢查和設置操作原子... – cheeken 2013-02-21 05:50:21

+1

檢查'ConcurrentMap'接口,特別是它的'putIfAbsent()'方法。 – erickson 2013-02-21 05:55:53

+0

埃裏克森,是的。上面的代碼可以優化。我試圖專注於邏輯。 – JRR 2013-02-21 05:59:04

0

大概是面試官對Ernest Friedman-Hill提出的解決方案感興趣。但是,由於其缺點,它通常不能用於生產代碼。有一次,我寫了下面的同步工具來處理這個問題:

package com.paypal.risk.ars.dss.framework.concurrent; 

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


public class MultiLock <K>{ 
    private ConcurrentHashMap<K, ReentrantLock> locks = new ConcurrentHashMap<K, ReentrantLock>(); 

    /** 
    * Locks on a given id. 
    * Make sure to call unlock() afterwards, otherwise serious bugs may occur. 
    * It is strongly recommended to use try{ }finally{} in order to guarantee this. 
    * Note that the lock is re-entrant. 
    * @param id The id to lock on 
    */ 
    public void lock(K id) { 
     while (true) { 
      ReentrantLock lock = getLockFor(id); 
      lock.lock(); 
      if (locks.get(id) == lock) 
       return; 
      else // means that the lock has been removed from the map by another thread, so it is not safe to 
       // continue with the one we have, and we must retry. 
       // without this, another thread may create a new lock for the same id, and then work on it. 
       lock.unlock(); 
     } 
    } 

    /** 
    * Unlocks on a given id. 
    * If the lock is not currently held, an exception is thrown. 
    * 
    * @param id The id to unlock 
    * @throws IllegalMonitorStateException in case that the thread doesn't hold the lock 
    */ 
    public void unlock(K id) { 
     ReentrantLock lock = locks.get(id); 
     if (lock == null || !lock.isHeldByCurrentThread()) 
      throw new IllegalMonitorStateException("Lock for " + id + " is not owned by the current thread!"); 
     locks.remove(id); 
     lock.unlock(); 
    } 

    private ReentrantLock getLockFor(K id) { 
     ReentrantLock lock = locks.get(id); 
     if (lock == null) { 
      lock = new ReentrantLock(); 
      ReentrantLock prevLock = locks.putIfAbsent(id, lock); 
      if (prevLock != null) 
       lock = prevLock; 
     } 
     return lock; 
    } 
} 

注意,它可以更簡單地用一個簡單的地圖和一個全局鎖來實現。但是,爲了提高吞吐量,我想避免全局鎖定。