基於線程池的多線程售票demo2.0(原創)

繼上回基於線程池的多線程售票demo,具體連接: http://www.cnblogs.com/xifenglou/p/8807323.html
以上算是單機版的實現,
至於分佈式的項目就不能知足了,因此特別研究了一翻,採用redis 實現分佈式鎖機制, 實現了2.0版本。

使用redis setNx getSet方法 實現分佈式鎖,獲取到鎖的線程 將進行售票核心業務操做,具體見代碼,歡迎討論!
一.redis命令講解:
 setnx()命令:

setnx的含義就是SET if Not Exists,其主要有兩個參數 setnx(key, value)。html

該方法是原子的,若是key不存在,則設置當前key成功,返回1;若是當前key已經存在,則設置當前key失敗,返回0。java

 get()命令:
get(key) 獲取key的值,若是存在,則返回;若是不存在,則返回nil;
 getset()命令:
  這個命令主要有兩個參數 getset(key, newValue)。該方法是原子的,對key設置newValue這個值,而且返回key原來的舊值。
假設key原來是不存在的,那麼屢次執行這個命令,會出現下邊的效果:
1. getset(key, "value1")  返回nil   此時key的值會被設置爲value1
      2. getset(key, "value2")  返回value1   此時key的值會被設置爲value2
      3. 依次類推!
二.具體的使用步驟以下:
     1. setnx(lockkey, 當前時間+過時超時時間) ,若是返回1,則獲取鎖成功;若是返回0則沒有獲取到鎖,轉向2。
     2. get(lockkey)獲取值oldExpireTime ,並將這個value值與當前的系統時間進行比較,若是小於當前系統時間,則認爲這個鎖已經超時,能夠容許別的請求從新獲取,轉向3。
     3. 計算newExpireTime=當前時間+過時超時時間,而後getset(lockkey, newExpireTime) 會返回當前lockkey的值currentExpireTime。
     4. 判斷currentExpireTime與oldExpireTime 是否相等,若是相等,說明當前getset設置成功,獲取到了鎖。若是不相等,說明這個鎖又被別的請求獲取走了,那麼當前請求能夠直接返回失敗,或者繼續重試。
  5. 在獲取到鎖以後,當前線程能夠開始本身的業務處理,當處理完畢後,比較本身的處理時間和對於鎖設置的超時時間,若是小於鎖設置的超時時間,則直接執行delete釋放鎖;若是大於鎖設置的超時時間,則不須要再鎖進行處理。


import org.springframework.util.StopWatch;
import redis.clients.jedis.Jedis;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* 使用redis
* setnx getset 方式 實現 分佈式鎖
*
*/
public class TicketRunnable implements Runnable {
private CountDownLatch count;
private CyclicBarrier barrier;
private static final Integer Lock_Timeout = 10000;
private static final String lockKey = "LockKey";
private volatile boolean working = true;

public TicketRunnable(CountDownLatch count,CyclicBarrier barrier) {
this.count = count;
this.barrier = barrier;
}

private int num = 20; // 總票數

public void sellTicket(Jedis jedis) {
try{
boolean getLock = tryLock(jedis,lockKey, Long.valueOf(10));
if(getLock){
// Do your job
if (num > 0) {
System.out.print("=============="+Thread.currentThread().getName()+"=============== 售出票號" + num);
num--;
if(num!=0)
System.out.println(",還剩" + num + "張票--" );
else {
System.out.println(",票已經票完!--");
working = false;
}
}
}
}catch(Exception e){
System.out.println(e);
}finally {
try {
realseLock(jedis, lockKey);
Thread.sleep(600);
}catch (Exception e ) {
e.printStackTrace();
}
}

}

/**
* 獲取鎖
* @param jedis
* @param lockKey
* @param timeout
* @return
*/
public boolean tryLock(Jedis jedis,String lockKey, Long timeout) {
try {
Long currentTime = System.currentTimeMillis();//開始加鎖的時間
boolean result = false;

while (true && working) {
if ((System.currentTimeMillis() - currentTime) / 1000 > timeout) {//當前時間超過了設定的超時時間
System.out.println("---------------- try lock time out.");
break;
} else {
result = innerTryLock(jedis,lockKey);
if (result) {
System.out.println("=============="+Thread.currentThread().getName()+"=============== 獲取到鎖,開始工做!");
break;
} else {
System.out.println(Thread.currentThread().getName()+" Try to get the Lock,and wait 200 millisecond....");
Thread.sleep(200);
}
}
}
return result;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 釋放鎖
* @param jedis
* @param lockKey
*/
public void realseLock(Jedis jedis,String lockKey) {
if (!checkIfLockTimeout(jedis,System.currentTimeMillis(), lockKey)) {
jedis.del(lockKey);
System.out.println("=============="+Thread.currentThread().getName()+"=============== 釋放鎖!");
}
}

/**
* 獲取鎖具體實現
* @param jedis
* @param lockKey
* @return
*/
private boolean innerTryLock(Jedis jedis,String lockKey) {
long currentTime = System.currentTimeMillis();//當前時間
String lockTimeDuration = String.valueOf(currentTime + Lock_Timeout + 1);//鎖的持續時間
Long result = jedis.setnx(lockKey, lockTimeDuration);

if (result == 1) { //返回1 表明第1次設置
return true;
} else {
if (checkIfLockTimeout(jedis,currentTime, lockKey)) {
String preLockTimeDuration = jedis.getSet(lockKey, lockTimeDuration); //此處須要再判斷一次
if(preLockTimeDuration == null){ //若是 返回值 爲空, 表明獲取到鎖 不然 鎖被其餘線程捷足先登
return true;
}else{
if (currentTime > Long.parseLong(preLockTimeDuration)) {
return true;
}
}
}
return false;
}
}

/**
*
* @param jedis
* @param currentTime
* @param lockKey
* @return
*/
private boolean checkIfLockTimeout(Jedis jedis,Long currentTime, String lockKey) {
String value = jedis.get(lockKey);
if (value == null) {
return true;
}else{
if (currentTime > Long.parseLong(value)) {//當前時間超過鎖的持續時間
return true;
} else {
return false;
}
}

}



@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"到達,等待中...");
Jedis jedis = new Jedis("localhost", 6379);

try{
barrier.await(); // 此處阻塞 等全部線程都到位後一塊兒進行搶票
if(Thread.currentThread().getName().equals("pool-1-thread-1")){
System.out.println("---------------所有線程準備就緒,開始搶票----------------");
}else {
Thread.sleep(5);
}
while (num > 0) {
sellTicket(jedis);
}
count.countDown(); //當前線程結束後,計數器-1
}catch (Exception e){e.printStackTrace();}


}

/**
*
* @param args
*/
public static void main(String[] args) {
int threadNum = 5; //模擬多個窗口 進行售票
final CyclicBarrier barrier = new CyclicBarrier(threadNum);
final CountDownLatch count = new CountDownLatch(threadNum); // 用於統計 執行時長

StopWatch watch = new StopWatch();
watch.start();
TicketRunnable tickets = new TicketRunnable(count,barrier);
ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
//ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < threadNum; i++) { //此處 設置數值 受限於 線程池中的數量
executorService.submit(tickets);
}
try {
count.await();
executorService.shutdown();
watch.stop();
System.out.println("耗 時:" + watch.getTotalTimeSeconds() + "秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

運行結果以下:

 

火車票餘量 能夠在redis中獲取, redis

這樣就能夠模擬多進程 多線程方式  共同訪問redis中的變量。

 

 有問題歡迎留言 探討!spring

相關文章
相關標籤/搜索