欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

利用redis實現(xiàn)分布式鎖,快速解決高并發(fā)時的線程安全問題

 更新時間:2021年01月25日 10:13:28   作者:鹽城吊霸天  
這篇文章主要介紹了利用redis實現(xiàn)分布式鎖,快速解決高并發(fā)時的線程安全問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧

實際工作中,經(jīng)常會遇到多線程并發(fā)時的類似搶購的功能,本篇描述一個簡單的redis分布式鎖實現(xiàn)的多線程搶票功能。

直接上代碼。首先按照慣例,給出一個錯誤的示范:

我們可以看看,當20個線程一起來搶10張票的時候,會發(fā)生什么事。

package com.tiger.utils; 
public class TestMutilThread {
 
	// 總票量
	public static int count = 10; 
	public static void main(String[] args) {
		statrtMulti();
	}
 
	public static void statrtMulti() {
		for (int i = 1; i <= 20; i++) {
			TicketRunnable tickrunner = new TicketRunnable();
			Thread thread = new Thread(tickrunner, "Thread No: " + i);
			thread.start();
		} 
	}
 
	public static class TicketRunnable implements Runnable {
 
		@Override
		public void run() {
			System.out.println(Thread.currentThread().getName() + " start "
					+ count);
			// TODO Auto-generated method stub
			// logger.info(Thread.currentThread().getName()
			// + " really start" + count);
			if (count <= 0) {
				System.out.println(Thread.currentThread().getName()
						+ " ticket sold out ! No tickets remained!" + count);
				return;
			} else {
				count = count - 1;
				System.out.println(Thread.currentThread().getName()
						+ " bought a ticket,now remaining :" + (count));
			}
		}
	}
}

測試結(jié)果,從結(jié)果可以看到,票數(shù)在不同的線程中已經(jīng)出現(xiàn)混亂。

Thread No: 2 start 10
Thread No: 6 start 10
Thread No: 4 start 10
Thread No: 5 start 10
Thread No: 3 start 10
Thread No: 9 start 6
Thread No: 1 start 10
Thread No: 1 bought a ticket,now remaining :3
Thread No: 9 bought a ticket,now remaining :4
Thread No: 3 bought a ticket,now remaining :5
Thread No: 12 start 3
Thread No: 5 bought a ticket,now remaining :6
Thread No: 4 bought a ticket,now remaining :7
Thread No: 8 start 7
Thread No: 7 start 8
Thread No: 12 bought a ticket,now remaining :1
Thread No: 14 start 0
Thread No: 6 bought a ticket,now remaining :8
Thread No: 16 start 0
Thread No: 2 bought a ticket,now remaining :9
Thread No: 16 ticket sold out ! No tickets remained!0
Thread No: 14 ticket sold out ! No tickets remained!0
Thread No: 18 start 0
Thread No: 18 ticket sold out ! No tickets remained!0
Thread No: 7 bought a ticket,now remaining :0
Thread No: 15 start 0
Thread No: 8 bought a ticket,now remaining :1
Thread No: 13 start 2
Thread No: 19 start 0
Thread No: 11 start 3
Thread No: 11 ticket sold out ! No tickets remained!0
Thread No: 10 start 3
Thread No: 10 ticket sold out ! No tickets remained!0
Thread No: 19 ticket sold out ! No tickets remained!0
Thread No: 13 ticket sold out ! No tickets remained!0
Thread No: 20 start 0
Thread No: 20 ticket sold out ! No tickets remained!0
Thread No: 15 ticket sold out ! No tickets remained!0
Thread No: 17 start 0
Thread No: 17 ticket sold out ! No tickets remained!0

為了解決多線程時出現(xiàn)的混亂問題,這里給出真正的測試類!!!

真正的測試類,這里啟動20個線程,來搶10張票。

RedisTemplate 是用來實現(xiàn)redis操作的,由spring進行集成。這里是使用到了RedisTemplate,所以我以構(gòu)造器的形式在外部將RedisTemplate傳入到測試類中。

MultiTestLock 是用來實現(xiàn)加鎖的工具類。

總票數(shù)使用volatile關(guān)鍵字,實現(xiàn)多線程時變量在系統(tǒng)內(nèi)存中的可見性,這點可以去了解下volatile關(guān)鍵字的作用。

TicketRunnable用于模擬搶票功能。

其中由于lock與unlock之間存在if判斷,為保證線程安全,這里使用synchronized來保證。

測試類:

package com.tiger.utils; 
import java.io.Serializable; 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate; 
public class MultiConsumer {
	Logger logger=LoggerFactory.getLogger(MultiTestLock.class);	
	private RedisTemplate<Serializable, Serializable> redisTemplate;	
	public MultiTestLock lock;
	//總票量
	public volatile static int count = 10;
 
	public void statrtMulti() {
		lock = new MultiTestLock(redisTemplate);
		for (int i = 1; i <= 20; i++) {
			TicketRunnable tickrunner = new TicketRunnable();
			Thread thread = new Thread(tickrunner, "Thread No: " + i);
			thread.start();
			} 
	}
 
	public class TicketRunnable implements Runnable {
 
		@Override
		public void run() {
			logger.info(Thread.currentThread().getName() + " start "
					+ count);
			// TODO Auto-generated method stub
			if (count > 0) {
//				logger.info(Thread.currentThread().getName()
//						+ " really start" + count);
				lock.lock();
				synchronized (this) {
					if(count<=0){
						logger.info(Thread.currentThread().getName()
								+ " ticket sold out ! No tickets remained!" + count);
						lock.unlock();
						return;
					}else{
						count=count-1;
						logger.info(Thread.currentThread().getName()
								+ " bought a ticket,now remaining :" + (count));
					}
				}
				lock.unlock();
			}else{
				logger.info(Thread.currentThread().getName()
						+ " ticket sold out !" + count);
			}
		}
	}
 
	public RedisTemplate<Serializable, Serializable> getRedisTemplate() {
		return redisTemplate;
	}
 
	public void setRedisTemplate(
			RedisTemplate<Serializable, Serializable> redisTemplate) {
		this.redisTemplate = redisTemplate;
	}
 
	public MultiConsumer(RedisTemplate<Serializable, Serializable> redisTemplate) {
		super();
		this.redisTemplate = redisTemplate;
	}
}

Lock工具類:

我們知道為保證線程安全,程序中執(zhí)行的操作必須時原子的。redis后續(xù)的版本中可以使用set key同時設(shè)置expire超時時間。

想起上次去 電信翼支付 面試時,面試官問過一個問題:分布式鎖如何防止死鎖,問題關(guān)鍵在于我們在分布式中進行加鎖操作時成功了,但是后續(xù)業(yè)務(wù)操作完畢執(zhí)行解鎖時出現(xiàn)失敗。導致分布式鎖無法釋放。出現(xiàn)死鎖,后續(xù)的加鎖無法正常進行。所以這里設(shè)置expire超時時間的目的就是防止出現(xiàn)解鎖失敗的情況,這樣,即使解鎖失敗了,分布式鎖依然會在超時時間過了之后自動釋放。

具體在代碼中也有注釋,也可以作為參考。

package com.tiger.utils; 
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock; 
import javax.sound.midi.MidiDevice.Info; 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.script.RedisScript; 
 
public class MultiTestLock implements Lock {	
	Logger logger=LoggerFactory.getLogger(MultiTestLock.class);	
	private RedisTemplate<Serializable, Serializable> redisTemplate;	
	public MultiTestLock(RedisTemplate<Serializable, Serializable> redisTemplate) {
		super();
		this.redisTemplate = redisTemplate;
	}
 
	@Override
	public void lock() {
		//這里使用while循環(huán)強制線程進來之后先進行搶鎖操作。只有搶到鎖才能進行后續(xù)操作
		while(true){
			if(tryLock()){
				try {
					//這里讓線程睡500毫秒的目的是為了模擬業(yè)務(wù)耗時,確保業(yè)務(wù)結(jié)束時之前設(shè)置的值正好打到超時時間,
					//實際生產(chǎn)中可能有偏差,這里需要經(jīng)驗
					Thread.sleep(500l);
//					logger.info(Thread.currentThread().getName()+" time to awake");
					return;
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}else{
				try {
					//這里設(shè)置一個隨機毫秒的sleep目的時降低while循環(huán)的頻率 
					Thread.sleep(new Random().nextInt(200)+100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
 
	@Override
	public boolean tryLock() {
		//這里也可以選用transactionSupport支持事務(wù)操作
		SessionCallback<Object> sessionCallback=new SessionCallback<Object>() {
			@Override
			public Object execute(RedisOperations operations)
					throws DataAccessException {
				operations.multi();
				operations.opsForValue().setIfAbsent("secret", "answer");
				//設(shè)置超時時間要根據(jù)業(yè)務(wù)實際的可能處理時間來,是一個經(jīng)驗值
				operations.expire("secret", 500l, TimeUnit.MILLISECONDS);
				Object object=operations.exec();
				return object;
			}
		};
		//執(zhí)行兩部操作,這里會拿到一個數(shù)組值 [true,true],分別對應上述兩部操作的結(jié)果,如果中途出現(xiàn)第一次為false則表明第一步set值出錯
		List<Boolean> result=(List) redisTemplate.execute(sessionCallback);
//		logger.info(Thread.currentThread().getName()+" try lock "+ result);
		if(true==result.get(0)||"true".equals(result.get(0)+"")){
			logger.info(Thread.currentThread().getName()+" try lock success");
			return true;
		}else{
			return false;
		}
	}
 
	@Override
	public boolean tryLock(long arg0, TimeUnit arg1)
			throws InterruptedException {
		// TODO Auto-generated method stub
		return false;
	}
 
	@Override
	public void unlock() {
		//unlock操作直接刪除鎖,如果執(zhí)行完還沒有達到超時時間則直接刪除,讓后續(xù)的線程進行繼續(xù)操作。起到補刀的作用,確保鎖已經(jīng)超時或被刪除
		SessionCallback<Object> sessionCallback=new SessionCallback<Object>() {
			@Override
			public Object execute(RedisOperations operations)
					throws DataAccessException {
				operations.multi();
				operations.delete("secret");
				Object object=operations.exec();
				return object;
			}
		};
		Object result=redisTemplate.execute(sessionCallback);
	} 
 
	@Override
	public void lockInterruptibly() throws InterruptedException {
		// TODO Auto-generated method stub
	}
 
	@Override
	public Condition newCondition() {
		// TODO Auto-generated method stub
		return null;
	}
	
	public RedisTemplate<Serializable, Serializable> getRedisTemplate() {
		return redisTemplate;
	}
 
	public void setRedisTemplate(
			RedisTemplate<Serializable, Serializable> redisTemplate) {
		this.redisTemplate = redisTemplate;
	}
}

執(zhí)行結(jié)果

可以看到,票數(shù)穩(wěn)步減少,后續(xù)沒有搶到鎖的線程余票為0,無票可搶。

tips:

這其中也出現(xiàn)了一個問題,redis進行多部封裝操作時,系統(tǒng)報錯:ERR EXEC without MULTI

后經(jīng)過查閱發(fā)現(xiàn)問題出在:

在spring中,多次執(zhí)行MULTI命令不會報錯,因為第一次執(zhí)行時,會將其內(nèi)部的一個isInMulti變量設(shè)為true,后續(xù)每次執(zhí)行命令是都會檢查這個變量,如果為true,則不執(zhí)行命令。

而多次執(zhí)行EXEC命令則會報開頭說的"ERR EXEC without MULTI"錯誤。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。如有錯誤或未考慮完全的地方,望不吝賜教。

相關(guān)文章

  • 了解Redis常見應用場景

    了解Redis常見應用場景

    Redis是一個key-value存儲系統(tǒng),現(xiàn)在在各種系統(tǒng)中的使用越來越多,大部分情況下是因為其高性能的特性,被當做緩存使用,這里介紹下Redis經(jīng)常遇到的使用場景
    2021-06-06
  • CentOS系統(tǒng)中Redis數(shù)據(jù)庫的安裝配置指南

    CentOS系統(tǒng)中Redis數(shù)據(jù)庫的安裝配置指南

    Redis是一個基于主存存儲的數(shù)據(jù)庫,性能很強,這里我們就來看一下CentOS系統(tǒng)中Redis數(shù)據(jù)庫的安裝配置指南,包括將Redis作為系統(tǒng)服務(wù)運行的技巧等,需要的朋友可以參考下
    2016-06-06
  • Redis 刪除策略的三種實現(xiàn)

    Redis 刪除策略的三種實現(xiàn)

    本文主要介紹了Redis 刪除策略的三種實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-06-06
  • Redis有序集合類型的操作_動力節(jié)點Java學院整理

    Redis有序集合類型的操作_動力節(jié)點Java學院整理

    今天通過本文給大家說一下Redis中最后一個數(shù)據(jù)類型 “有序集合類型”,需要的的朋友參考下吧
    2017-08-08
  • springboot +redis 實現(xiàn)點贊、瀏覽、收藏、評論等數(shù)量的增減操作

    springboot +redis 實現(xiàn)點贊、瀏覽、收藏、評論等數(shù)量的增減操作

    這篇文章主要介紹了springboot +redis 實現(xiàn)點贊、瀏覽、收藏、評論等數(shù)量的增減操作,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-09-09
  • Redis中scan命令的深入講解

    Redis中scan命令的深入講解

    這篇文章主要給大家介紹了關(guān)于Redis中scan命令的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用redis具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-10-10
  • redis+lua實現(xiàn)限流的項目實踐

    redis+lua實現(xiàn)限流的項目實踐

    redis有很多限流的算法(比如:令牌桶,計數(shù)器,時間窗口)等,在分布式里面進行限流的話,我們則可以使用redis+lua腳本進行限流,下面就來介紹一下redis+lua實現(xiàn)限流
    2023-10-10
  • Spring boot+redis實現(xiàn)消息發(fā)布與訂閱的代碼

    Spring boot+redis實現(xiàn)消息發(fā)布與訂閱的代碼

    這篇文章主要介紹了Spring boot+redis實現(xiàn)消息發(fā)布與訂閱,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值需要的朋友可以參考下
    2020-04-04
  • Redis妙用之存儲用戶token問題

    Redis妙用之存儲用戶token問題

    這篇文章主要介紹了Redis妙用之存儲用戶token問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • Redis緩存三大異常的處理方案梳理總結(jié)

    Redis緩存三大異常的處理方案梳理總結(jié)

    這篇文章主要介紹了Redis緩存三大異常的處理方案梳理總結(jié),緩存方式,在提高數(shù)據(jù)查詢效率、保護數(shù)據(jù)庫等方面起到了不可磨滅的作用,但實際應用中,可能會出現(xiàn)一些Redis緩存異常的情況,下文對其方案總結(jié)需要的朋友可以參考一下
    2022-06-06

最新評論