머리말
단일 JVM 내의 동기화는 처리하기 쉽습니다. JDK가 제공하는 잠금을 직접 사용하되 크로스 프로세스 동기화는 불가능합니다. 이 경우 제 3 자에게 의존해야합니다. 나는 여기에 Redis를 사용하고 있으며 물론 다른 많은 구현 방법이 있습니다. 실제로 Redis 구현을 기반으로 한 원리는 매우 간단합니다. 코드를 읽기 전에 먼저 원리를 확인하는 것이 좋습니다. 코드를 읽은 후에는 이해하기 쉬워야합니다.
JDK의 java.util.concurrent.locks.Lock 인터페이스를 구현하지는 않지만 JDK에는 newCondition 메소드가 있기 때문에 사용자 정의하십시오. 나는 당분간 그것을 구현하지 않았습니다. 이 잠금은 5 가지 변형의 잠금 방법을 제공합니다. 잠금을 얻기 위해 사용할 것을 선택할 수 있습니다. 내 생각은 시간 초과 반환과 함께 메소드를 사용하는 것이 가장 좋습니다. 그렇지 않은 경우, Redis가 매달려 있으면 스레드가 항상 죽은 루프에있을 것입니다 (이것에 대해 더 최적화해야합니다. Redis의 운영이 예외를 제외하고 나올 것입니다. Redis가 중단 될 때이 잠금을 사용하는 사용자에게 알리는 메커니즘을 정의 할 수 있습니다).
패키지 cc.lixiaohui.lock; import java.util.concurrent.timeUnit; public 인터페이스 잠금 { / *** 차단 획득 잠금, 인터럽트에 응답하지 않음* / void lock; / ** * 획득 잠금 차단, 인터럽트에 응답하지 않음 * * @Throws InterruptedException */ void lockinterpruptility trows InterruptedException; / *** 잠금을 얻으려고 노력하고 잠금을 얻지 않고 즉시 반환하십시오. / ** * 차단 획득 잠금 장치는 타임 아웃으로 자동으로 반환되며, 인터럽트에 응답하지 않으면 서 * * @param time * @param init * @return {@code true} 잠금이 성공적으로 획득되면 {@code false} 지정된 시간 내에 잠금이 검색되지 않으면 * */ boolean trylock (긴 시간, 타임 터니 유닛); / *** 타임 아웃, 응답 인터럽트** @param time* @param init* @param init* @param init {@code true} 차단 획득 잠금 장치는 잠금이 성공적으로 획득되면 {@code false} 지정된 시간 내에 잠금이 검색되지 않으면 잠금이 중단되기 위해 노력하는 현재 스레드가 획득 된 시간이 중단됩니다. 중단 예고를 던집니다. / *** 릴리스 잠금*/ 무효 잠금 해제; }추상적 구현을보십시오.
패키지 cc.lixiaohui.lock; import java.util.concurrent.timeUnit;/*** 잠금의 골격 구현, 잠금을 얻는 실제 단계는 서브 클래스에 의해 구현됩니다. * * @Author lixiaohui * /public acblack class AbstractLock은 잠금 잠금을 구현 { /** * <fre> * 여기에 가시성이 보장 될 가치가 있는지 논의 할 가치가 있는지 여부는 논의 할 가치가 있는지, * 1. * 1. 동일한 잠금 객체의 여러 스레드가 다른 객체의 여러 스레드를 사용하는 것이 가능합니다. 동일한 잠금성을 보장 할 필요가 없습니다. 보장. * </ pre> */ 보호 된 휘발성 부울 잠금; / ** * 현재 JVM에서 잠금 장치를 들고있는 스레드 (하나가있는 경우) */ private 스레드 독점 소유자를 읽습니다. public void lock {try {lock (false, 0, null, false); } catch (InterpruptedException e) {// todo incor}} public void leopruptibly trows interruptedException {lock (false, 0, null, true); } public boolean trylock (오랜 시간, 시간 유닛 단위) {try {return lock (true, time, unit, false); } catch (InterpruptedException e) {// todo incore} false를 반환합니다. } public boolean trylockinterruptibly (오랜 시간, TimeUnit Unit)는 InterruptedException {return lock (True, Time, Unit, True); } public void unlock {// todo 현재 스레드가 잠금을 유지하는지 확인합니다. } unlock0; setexcluboryownerthread (null); } 보호 된 void setExcluctionownerthread (스레드 스레드) {ExclusiveNerthread = Thread; } 보호 된 최종 스레드 getExcluctionOwnerThread {return 독점 거주자 스레드; } 보호 된 초록 void unlock0; / ** * 획득 차단 구현 * * @param usetimeout * @param time * @param init * @param 인터럽트 인터럽트에 응답할지 여부 * @return * @throws InterruptedException */ 보호 된 초록 부울 잠금 (부울 usetimeout, 오랜 시간, 시간 단위, 부울 트로그 트로우) Redis의 최종 구현을 기반으로 잠금을 획득하고 릴리스하기위한 키 코드는 lock 방법 과이 클래스의 unlock0 방법에 있습니다. 이 두 가지 방법 만 살펴보고 혼자서 완전히 쓸 수 있습니다.
패키지 cc.lixiaohui.lock; import java.util.concurrent.timeUnit; import redis.clients.jedis.jedis import redis.clients.jedis.jedis;/** * <fre> * redis를 기반으로 Setnx 작업에 의해 구현 된 분산 잠금 장치 * * 스레드가 모든 시간을 획득 할 때 잠금 장치를 사용하는 것이 가장 좋습니다. href = "http://redis.io/commands/setnx"> setnc 운영 참조 </a> * </pre> * * @author lixiaohui * */public class redisbaseddipributeedlock accerstends actractlock {private jedis jedis; // 잠금 보호 문자열 Lockey의 이름; // 잠금 장치의 유효 기간 (MS) 보호 긴 긴 Lockexpires; 공개 redisbaseddipributeDlock (Jedis Jedis, String Lockey, Long Lockexpires) {this.jedis = jedis; this.lockey = lockey; this.lockexpires = lockexpires; } // 차단 획득 잠금 장치 구현 Protected Boolean Lock (부울 Usetimeout, Long Time, TimeUnit Unit, Boolean Interrupt)는 InterruptedException {if (인터럽트) {Check -interruption; } long start = System.CurrentTimeMillis; 긴 타임 아웃 = unit.tomillis (시간); // if! usetimeout이면 (usetimeout? istimeout (start, timeout) : true) {if (인터럽트) {check -interruption; } long lockexpiretime = system.currenttimeMillis + lockexpires + 1; // 시간 초과 잠금 문자열 Stringoflockexpiretime = string.valueof (lockexpiretime); if (jedis.setnx (lockkey, stringoflockexpiretime) == 1) {// 획득 된 잠금 // todo가 잠금을 성공적으로 얻었고, 관련 식별자 잠금 = true를 설정합니다. setexcluberownerthread (thread.currentthread); 진실을 반환하십시오. } 문자열 값 = jedis.get (Lockey); if (value! = null && istimeexpired (value)) {// 잠금이 만료 됨 // 여러 스레드 (비 단일 JVM) 동시에 여기에 오면 문자열 oldValue = jedis.getset (lockkey, stringoflockexpiretime); // getSet은 //이지만 여기에있을 때 각 스레드가 얻은 OldValue는 동일하게 불가능합니다 (getset이 원자체이기 때문에) // 결합하여 얻은 OldValue는 여전히 만료 되었다면, (OldValue! = null && istimeexpired (//) {// try, try wortant recomed (oldvalue) {// istimeexpired (// setexcluberownerthread (thread.currentthread); 진실을 반환하십시오. }} else {// TODO 잠금이 만료되지 않았으며 다음 루프 재 시도를 입력하십시오}} false를 반환합니다. } public boolean trylock {long lockexpiretime = system.currenttimeMillis + lockexpires + 1; // 시간 초과 시간 문자열 StringofLockexpiretime = string.valueof (locexpiretime); if (jedis.setnx (lockkey, stringoflockexpiretime) == 1) {// 잠금을 얻으십시오 // todo는 잠금을 성공적으로 획득하고 관련 식별자 잠금 = true를 설정합니다. setexcluberownerthread (thread.currentthread); 진실을 반환하십시오. } 문자열 값 = jedis.get (Lockey); if (value! = null && istimeexpired (value)) {// 잠금이 만료 됨 // 여러 스레드 (단일 jvm 아님) 동시에 여기에 오시기 때문에 String oldvalue = jedis.getset (lockkey, stringoflockexpiretime); // getSet은 //이지만 여기에있을 때 각 스레드에서 얻은 OldValue는 확실히 불가능합니다 (GetSet이 원자체이기 때문에) // OldValue가 여전히 만료되면 (OldValue! = null && istimeexpired (OldValue)) {// 잠금을 얻었을 때, istimeexpired (OldValue))가있는 경우 잠금을 얻었습니다. setexcluberownerthread (thread.currentthread); 진실을 반환하십시오. }} else {// TODO 잠금이 만료되지 않았으며 다음 루프 재 시도를 입력하십시오} return false; } /*** 쿼리이 잠금이 모든 스레드에 의해 유지되면 쿼리. * * * @return {@code true} 스레드 가이 잠금을 보유하고 * {@code false} 그렇지 않으면 */ public boolean islocked {if (locked) {return true; } else {String value = jedis.get (Lockkey); // 여기에는 실제로 문제가 있습니다. 생각 : get 메소드가 값을 반환 할 때, 값이 만료되었다고 가정하면,이 순간에 다른 노드가 값을 설정하고 잠금이 다른 스레드 (노드 보류)에 의해 유지되고 다음 판단은이 상황을 감지 할 수 없습니다. 그러나이 문제는 다른 문제를 일으키지 않아야합니다.이 방법의 목적은 //이 동기 제어가 아니기 때문에 잠금 상태에 대한 보고서 일뿐입니다. return! istimeexpired (value); }} @override protected void unlock0 {// todo는 잠금이 문자열 값을 만료하는지 여부를 결정합니다. = jedis.get (lockkey); if (! istimeexpired (value)) {dounlock; }} private void checkinterruption trows interruptedException {if (ride.currentthread.isinterrupted) {throw new InterruptedException; }} private boolean istimeexpired (문자열 값) {return long.parselong (value) <System.CurrentTimeMillis; } private boolean istimeout (Long Start, Long Timeout) {return start + timeout> system.currenttimemillis; } private void dounlock {jedis.del (Lockey); }} 향후 구현 방법 (예 : zookeeper 등)을 변경하면 AbstractLock 직접 상속하고 L ock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt) 및 unlock0 메소드 (소위 추상화)를 구현할 수 있습니다.
시험
글로벌 ID 재배자를 시뮬레이션하고 IDGenerator 클래스를 설계하십시오. 이 클래스는 글로벌 증분 ID를 생성하는 책임이 있습니다. 코드는 다음과 같습니다.
패키지 cc.lixiaohui.lock; import java.math.biginteger; import java.util.concurrent.timeUnit;/** * ID 생성 시뮬레이션 * @Author lixiaoHui * */public class idgenerator {개인 정적 고정 인테거 ID = biginteger.valueof (0); 개인 최종 잠금 잠금 장치; 개인 정적 최종 BigInteger 증분 = biginteger.valueof (1); public idgenerator (잠금 잠금) {this.lock = 잠금; } public String getAndIncrement {if (lock.rylock (3, timeUnit.seconds)) {try {// todo 여기에서 잠금을 가져 와서 임계 영역 리소스 리턴 getAndIncrement0에 액세스합니다. } 마침내 {lock.unlock; }} return null; // retud getAndIncrement0; } private String getAndIncrement0 {String s = id.toString; id = id.add (증분); 반환 s; }} 테스트 메인 로직 : 데드 루프에서 동일한 JVM에서 두 개의 스레드가 열립니다 (루프 사이에 간격이 없으며 테스트가 의미가 없으면 테스트가 의미가 없음) ID 얻을 수 있습니다 (DEAD 루프는 아니지만 20 초 동안 실행). ID를 가져 와서 동일한 Set 에 저장합니다. 저장되기 전에 ID set 에 존재하는지 확인하십시오. 이미 존재하면 두 스레드를 멈추게하십시오. 프로그램이 정상적으로 20 초를 실행할 수 있다면이 분산 잠금 장치가 요구 사항을 충족 할 수 있음을 의미합니다. 이러한 테스트의 효과는 다른 JVMS의 효과 (즉, 실제 분산 환경에서)와 동일해야합니다. 다음은 테스트 클래스의 코드입니다.
패키지 cc.lixiaohui.distributeedlock.distributedLock; import java.util.hashset; import java.util.set; import org.junit.test; import redis.clients.jedis.jedis; import cc.lixiaohui.lock.idgenerator; import cc.lixiaoohui.lock; cc.lixiaohui.lock.redisbaseddistributedLock; public class idgeneratortest {private static set <string> genderatedIds = new Hashset <String>; 비공개 정적 최종 문자열 lock_key = "lock.lock"; 개인 정적 최종 최종 Long Lock_expire = 5 * 1000; @Test Public Void Test Throws InterruptedException {Jedis Jedis1 = New Jedis ( "LocalHost", 6379); 잠금 잠금 1 = 새로운 redisbaseddistributedLock (jedis1, lock_key, lock_expire); Idgenerator g1 = 새로운 Idgenerator (Lock1); Idconsumemission socume1 = 새로운 Idconsumemission (g1, "sucume1"); Jedis Jedis2 = New Jedis ( "Localhost", 6379); Lock Lock2 = 새로운 redisbaseddipributedLock (jedis2, lock_key, lock_expire); Idgenerator g2 = 새로운 Idgenerator (Lock2); Idconsumemission socume2 = 새로운 idconsumemission (g2, "sucume2"); 스레드 T1 = 새 스레드 (COUME1); 스레드 t2 = 새로운 스레드 (coome2); T1. 스테이트; t2. 스테이트; Thread.sleep (20 * 1000); // 20 초 동안 두 개의 스레드가 실행되도록합니다. idconsumemission.stop; T1. 조인; t2. 조인; } 정적 문자열 시간 {return string.valueof (System.CurrentTimeMillis / 1000); } 정적 클래스 idconsumemission emplements runnable {private idgenerator idgenerator; 개인 문자열 이름; 개인 정적 휘발성 부울 정지; public idconsumemission (idgenerator idgenerator, 문자열 이름) {this.idgenerator = idgenerator; this.name = 이름; } public static void stop {stop = true; } public void run {system.out.println (time + ": 소비" + name + "start"); while (! stop) {String id = idgenerator.getAndIncrement; if (generatedIds.contains (id)) {system.out.println (time + ": duply id 생성, id =" + id); 정지 = 참; 계속하다; } generatedIds.add (id); System.out.println (time + ": 소비" + name + "add id =" + id); } system.out.println (time + ": 소비" + name + "done"); }}}분명히, 여기서 두 개의 스레드를 멈추는 방식은 그리 좋지 않습니다. 편의를 위해이 작업을 수행했습니다. 단지 테스트 일 뿐이 므로이 작업을 수행하지 않는 것이 가장 좋습니다.
테스트 결과
20 대에 인쇄 된 것들이 너무 많습니다. 전면에 인쇄 된 것이 clear 달리기가 거의 완료된 경우에만 사용할 수 있습니다. 아래 스크린 샷. 이것은이 자물쇠가 정상적으로 작동한다는 것을 보여줍니다.
IDGererator 잠겨 있지 않으면 (즉, IDGererator 의 getAndIncrement 메소드가 내부적으로 id 얻을 때 잠금하지 않으면 테스트가 통과되지 않으며 중간에 중단 될 확률이 매우 높습니다. 다음은 잠금 장치가 잠겨 있지 않은 테스트 결과입니다.
1 초 미만이 걸립니다.
이것은 1 초 미만입니다.
결론
자, 위는 Redis를 기반으로 분산 잠금 장치를 구현하는 Java에 관한 것입니다. 문제를 발견하면 문제를 해결하기를 바랍니다. 이 기사가 공부하고 일하는 데 도움이되기를 바랍니다. 궁금한 점이 있으면 의사 소통을 위해 메시지를 남길 수 있습니다.