鍍金池/ 教程/ Java/ 分布式鎖的簡單實現(xiàn)
分布式鎖的簡單實現(xiàn)
關(guān)于框架體系與戰(zhàn)術(shù)的思考
開源與中小型軟件公司的未來趨勢
生態(tài)圈的建立
用200行的DBF解析器來展示良好架構(gòu)設(shè)計
緣起
業(yè)務(wù)流程引擎設(shè)計
軟件開發(fā)雜談
高屋建瓴,理念先行
借船下海還是造船下海
Web界面快速開發(fā)實踐
教計算機程序解數(shù)學(xué)題
量身定制規(guī)則引擎,適應(yīng)多變業(yè)務(wù)場景
緩存相關(guān)代碼的演變
理想的開源框架與設(shè)計原則
框架2.0的設(shè)計梳理
與屈原對話及開源精神

分布式鎖的簡單實現(xiàn)

分布式鎖在分布式應(yīng)用當中是要經(jīng)常用到的,主要是解決分布式資源訪問沖突的問題。 一開始考慮采用ReentrantLock來實現(xiàn),但是實際上去實現(xiàn)的時候,是有問題的,ReentrantLock的lock和unlock要求必須是在同一線程進行,而分布式應(yīng)用中,lock和unlock是兩次不相關(guān)的請求,因此肯定不是同一線程,因此導(dǎo)致無法使用ReentrantLock。

接下來就考慮采用自己做個狀態(tài)來進行鎖狀態(tài)的記錄,結(jié)果發(fā)現(xiàn)總是死鎖,仔細一看代碼,能不鎖死么。

public synchronized void lock(){  
    while(lock){  
        Thread.sleep(1);  
    }  
    lock=true;  
...  
}  

public synchronized void unlock(){  
    lock=false;  
...  
}  

第一個請求要求獲得鎖,好么,給他個鎖定狀態(tài),然后他拿著鎖去干活了。

這個時候,第二個請求也要求鎖,OK,他在lock中等待解鎖。

第一個干完活了,過來還鎖了,這個時候悲催了,因為,他進不了unlock方法了。

可能有人會問,為什么采用while,而不是采用wait...notify?這個問題留一下,看看有人能給出來不?

總之,上面的方安案流產(chǎn)了。

同樣,不把synchronized 放在方法上,直接放在方法里放個同步對象可以不??道理是一樣的,也會發(fā)生上面一樣的死鎖。

到此為止前途一片黑暗。

@沈?qū)W良 同學(xué)的http://my.oschina.net/shenxueliang/blog/135865寫了一個用zk做的同布鎖,感覺還是比較復(fù)雜的且存疑。自己做不出來吧,又不死心。

再來看看Lock的接口,想了一下,不遵守Lock的接口了。編寫了下面的接口。

public interface DistributedLock extends RemoteObject {  

    long lock() throws RemoteException, TimeoutException;  

    long tryLock(long time, TimeUnit unit) throws RemoteException, TimeoutException;  

    void unlock(long token) throws RemoteException;  

}  

呵呵,眼尖的同學(xué)可能已經(jīng)發(fā)現(xiàn)不同了。

lock方法增加了個long返回值,tryLock方法,返回的也不是boolean,也是long,unlock方法多了一個long參數(shù)型參數(shù),呵呵,技巧就在這里了。

public class DistributedLockImpl extends UnicastRemoteObject implements DistributedLock {  
    /** 
     * 超時單位 
     */  
    private TimeUnit lockTimeoutUnit = TimeUnit.SECONDS;  
    /** 
     * 鎖的令牌 
     */  
    private volatile long token = 0;  
    /** 
     * 同步對象 
     */  
    byte[] lock = new byte[0];  
    /** 
     * 默認永不超時 
     */  
    long lockTimeout = 60 * 60;//默認超時3600秒  
    long beginLockTime;//獲取令牌時間,單位毫秒  

    public DistributedLockImpl() throws RemoteException {  
        super();  
    }  

    /** 
     * @param lockTimeout 鎖超時時間,如果加鎖的對象不解鎖,超時之后自動解鎖 
     * @param lockTimeoutUnit  
     * @throws RemoteException 
     */  
    public DistributedLockImpl(long lockTimeout, TimeUnit lockTimeoutUnit) throws RemoteException {  
        super();  
        this.lockTimeout = lockTimeout;  
        this.lockTimeoutUnit = this.lockTimeoutUnit;  
    }  
    public long lock() throws TimeoutException {  
        return tryLock(0, TimeUnit.MILLISECONDS);  
    }  
    private boolean isLockTimeout() {  
        if (lockTimeout <= 0) {  
            return false;  
        }  
        return (System.currentTimeMillis() - beginLockTime) < lockTimeoutUnit.toMillis(lockTimeout);  
    }  
    private long getToken() {  
        beginLockTime = System.currentTimeMillis();  
        token = System.nanoTime();  
        return token;  
    }  
    public long tryLock(long time, TimeUnit unit) throws TimeoutException {  
        synchronized (lock) {  
            long startTime = System.nanoTime();  
            while (token != 0 && isLockTimeout()) {  
                try {  
                    if (time > 0) {  
                        long endTime = System.nanoTime();  
                        if (endTime - startTime >= unit.toMillis(time)) {  
                            throw new TimeoutException();  
                        }  
                    }  
                    Thread.sleep(1);  
                } catch (InterruptedException e) {  
                    //DO Noting  
                }  
            }  
            return getToken();  
        }  
    }  
    public void unlock(long token) {  
        if (this.token != 0 && token == this.token) {  
            this.token = 0;  
        } else {  
            throw new RuntimeException("令牌" + token + "無效.");  
        }  
    }  
}  

下面對代碼進行一下講解。

上面的代碼提供了,永遠等待的獲取鎖的lock方法和如果在指定的時間獲取鎖失敗就獲得超時異常的tryLock方法,另外還有一個unlock方法。

技術(shù)的關(guān)鍵點實際上就是在token上,上面的實現(xiàn),有一個基本的假設(shè),就是兩次遠程調(diào)用之間的時間不可能在1納秒之內(nèi)完成。因此,每次鎖的操作都會返回一個長整型的令牌,就是當時執(zhí)行時間的納秒數(shù)。下次解鎖必須用獲得的令牌進行解鎖,才可以成功。如此,解鎖就不用添加同步操作了,從而解決掉上面死鎖的問題。

實際上,沒有令牌也是可以的,但是那樣就會導(dǎo)致a獲取了鎖,但是b執(zhí)行unlock也會成功解鎖,是不安全的,而加入令牌,就可以保證只有加鎖者才可以解鎖。

下面是測試代碼:

public class TestDLock {  
    public static void main(String[] args) throws Exception {  
        RmiServer rmiServer = new LocalRmiServer();  
        DistributedLockImpl distributedLock = new DistributedLockImpl();  
        rmiServer.registerRemoteObject("lock1", distributedLock);  
        MultiThreadProcessor processor = new MultiThreadProcessor("aa");  
        for (int i = 0; i < 8; i++) {  
            processor.addProcessor(new RunLock("aa" + i));  
        }  
        long s = System.currentTimeMillis();  
        processor.start();  
        long e = System.currentTimeMillis();  
        System.out.println(e - s);  
        rmiServer.unexportObject(distributedLock);  
    }  
}  

class RunLock extends AbstractProcessor {  
    public RunLock(String name) {  
        super(name);  
    }  

    @Override  
    protected void action() throws Exception {  
        try {  
            RmiServer client = new RemoteRmiServer();  
            DistributedLock lock = client.getRemoteObject("lock1");  
            for (int i = 0; i < 1000; i++) {  
                long token = lock.lock();  
                lock.unlock(token);  
            }  
            System.out.println("end-" + Thread.currentThread().getId());  
        } catch (RemoteException e) {  
            e.printStackTrace();  
        }  
    }  
}  

運行情況:

1    -0    [main] INFO   - 線程組<aa>運行開始,線程數(shù)8...
2    -3    [aa-aa0] INFO   - 線程<aa-aa0>運行開始...
3    -3    [aa-aa1] INFO   - 線程<aa-aa1>運行開始...
4    -3    [aa-aa2] INFO   - 線程<aa-aa2>運行開始...
5    -3    [aa-aa3] INFO   - 線程<aa-aa3>運行開始...
6    -3    [aa-aa4] INFO   - 線程<aa-aa4>運行開始...
7    -4    [aa-aa5] INFO   - 線程<aa-aa5>運行開始...
8    -4    [aa-aa6] INFO   - 線程<aa-aa6>運行開始...
9    -8    [aa-aa7] INFO   - 線程<aa-aa7>運行開始...
10   end-19
11   -9050 [aa-aa3] INFO   - 線程<aa-aa3>運行結(jié)束
12   end-17
13   -9052 [aa-aa1] INFO   - 線程<aa-aa1>運行結(jié)束
14   end-20
15   -9056 [aa-aa4] INFO   - 線程<aa-aa4>運行結(jié)束
16   end-16
17   -9058 [aa-aa0] INFO   - 線程<aa-aa0>運行結(jié)束
18   end-21
19   -9059 [aa-aa5] INFO   - 線程<aa-aa5>運行結(jié)束
20   end-26
21   -9063 [aa-aa7] INFO   - 線程<aa-aa7>運行結(jié)束
22   end-18
23   -9064 [aa-aa2] INFO   - 線程<aa-aa2>運行結(jié)束
24   end-22
25   -9065 [aa-aa6] INFO   - 線程<aa-aa6>運行結(jié)束
26   -9066 [main] INFO   - 線程組<aa>運行結(jié)束, 用時:9065ms
27   9069

也就是9069ms中執(zhí)行了8000次鎖定及解鎖操作。

小結(jié):

上面的分布式鎖實現(xiàn)方案,綜合考慮了實現(xiàn)簡單,鎖安全,鎖超時等因素。實際測試,大概900到1000次獲取鎖和釋放鎖操作每秒,可以滿足大多數(shù)應(yīng)用要求。