진화의 역사 레디 스의 Sike 자바 동기화 시리즈는 잠금 분산

문제

방법 (1) 레디 스는 잠금 분산 달성하기 위해?

장점 (2) 레디 스 분산 잠금 무엇입니까?

(3) 레디 스는 단점이 무엇인지 잠금을 분산?

(4) 레디 스는 바퀴가 사용할 수있는 기존의 분산 잠금이없는 실현?

간략한 소개

레디 스 (이름 : 원격 사전 서버 원격 사전 서비스) 형, 키 - 값 데이터베이스를 기록 할 수있는 메모리의 지속성에 따라 ANSI C, 지원 네트워크로 작성된 오픈 소스 사용하고, 다 언어 API를 제공한다.

이 장에서 우리는 설명 방법 (테스트 기반 레디 스의 분산 잠금 장치의 구현 및 설명 우리는 명확하게 (갑자기) 롱에게 배포 잠금 레디 스 인터뷰를하는 동안 말을 할 수 있도록 (분명히 야마을 처음부터 끝까지 구현의 진화의 역사)에 ) 펄스 (공식).

잠금 상태를 달성

잠금 (분산 잠금)에 위의 연구를 바탕으로, 우리는 자물쇠를 달성하기 위해 세 가지 조건이 있다는 것을 알고있다 :

ZookeeperLock에서 ReentrantLock와있는 상태의 값을 제어함으로써 달성 된 상태가이 상태의 식별 값은 고정되었는지 여부, (1) 상태 (공유) 변수는, 상기 자식 노드를 제어함으로써 달성된다 ;

큐 스레드를 저장하는 데 사용된다 (2) 큐, 큐에 AQS ReentrantLock와 의해 달성되며, 이는 ZookeeperLock의 자식 노드를 주문에 의해 달성되고;

(3) 웨이크 통해 실현된다 ZookeeperLock 감지 메커니즘은 AQS ReentrantLock와 방출 큐에 포함되는 자동 알람 스레드 후의 잠금을 해제 스레드에 다음 대기 스레드를 깨우기;

그런 다음 위의 세 가지 조건은 필요하지 않습니다?

공유 변수가 널인 경우 실제로 필요 조건 공유 가변 제어에 로크의 제 실현 사실이 아니다, 값 공유 변수가있는 경우 (자바는 CAS 동작 프로세스 공유 변수 내에서 사용될 수 있음) 그를 설정치 준 다음 (재시도), null 값이 다시 설정되는 로직 내에 고정 될 다음 공유 변수를 완성 값이 반복 여부를 확인한다.

한 라인에 공유 변수를 저장할뿐만 아니라 전체 시스템 (여러 프로세스)에만이 하나가 될 수 있도록 장소가있는 한, 분명하게 넣어합니다.

이는 분산 잠금 레디 스를 달성하기 위해 키 [대중 번호로이 문서 원래 "통 형제 소스 읽기"]입니다.

레디 스 잠금 진화의 역사를 배포

--set의 진화의 역사

지금은 우리가 공유 변수를 제어하는 ​​방법을 분산 공유 변수의 구현은 다음 레디 스, 제어 장소에 고정 할 필요가 말한다?

첫째, 우리는 레디 스 기본 명령이 / 할 델, 분산 잠금이 세 가지 명령에 의해 달성 될 수있다 / 설정 취득했다 알아? 물론.

반복

하는 잠금을 획득하기 전에 get lock_user_1그 다음 존재하지 않는 경우, 래치가 존재하지 않는 참조 set lock_user_1 value가 잠시 기다렸다가 다시 시도하는 것입니다, 그리고 마지막으로 재사용이 잠금 삭제 완료하면 del lock_user_1할 수 있습니다.

반복

이 잠금 장치가 동시에 두 개의 스레드를 가서이없는 시작할 경우,이 제도는, 문제가, 이번에는 복귀 후 두 스레드가 밖으로,이 시간을 설정 널 (전무)입니다 문제는, 두 개의 스레드가 성공적으로 동일한 잠금을 획득하기 위해 두 개의 스레드의 동등한를 설정할 수 있습니다.

따라서,이 솔루션은 가능하지 않습니다!

이 --setnx의 진화의 역사

위의 프로그램에 대한 주된 이유는 여러 스레드가 동시에 함께 그럼, 모두가 성공적으로 설정 가능하지 않습니다 setnx이다이 명령 set if not exist약어, 정해진이없는 경우 즉,이다.

반복

키가 같은 행동 setnx에 대해 반복 될 때 볼 수있는, 첫 번째 성공합니다.

따라서, 두 번째 방식이 사용하는 setnx lock_user_1 value그것, 잠금이 성공 의미는 다른 스레드가 성공적으로 실행하는 것을 의미하는 경우 0을 반환하는 경우 명령이 리턴 1, 그것은 몇 시간 동안 기다린 후 다시 시도, 그리고 마지막으로 같은 사용하는 것이 del lock_user_1릴리스를 잠금을.

반복

클라이언트가 잠금을 수행하는 방법에 깨진 얻을 경우,이 프로그램은 또한 문제가? 이것은 항상 그것을 배출하지 않는 고정되지 않는 이유는 무엇입니까? 예, 그것은 이것이다.

따라서,이 솔루션은 가능하지 않습니다!

의 진화 역사 세 --setnx + SETEX

위의 주된 이유는 내가 즉시 setnx 후 SETEX를 다시 실행할 수 있습니다, 잠금을 해제 할 수없는 클라이언트 분리 문제 후 잠금을 획득 할 수있는 옵션이 아니다?

대답은 '예, 이전 버전 2.6.12 우리 모두가 너무 재미 있습니다 분산 잠금 레디 스를 구현한다.

반복

따라서, 옵션 3 먼저 사용하는 것입니다 setnx lock_user_1 value잠금을 얻기 위해 명령을 한 다음 즉시 setex lock_user_1 30 value만료 시간을 설정하고, 마지막의 사용 del lock_user_1잠금이 해제됩니다.

그리고 높은 확률이 잠금을 획득 한 후 잠금을 해제하지 않는 클라이언트 단절의 문제를 해결하기 위해 수 있도록, setnx 이후에 잠겨 얻을 만료 시간을 설정 SETEX 수행합니다.

클라이언트가 탈옥됩니다 SETEX 전에 setnx 경우,이 프로그램은, 여전히 문제인가? 아 ~이 해결책을 보인다, 그러나이 확률은 아주 작은, 그래서 이전 버전 2.6.12 우리 이렇게 사용되는 모든, 거의 문제가 있었다.

따라서,이 기본 계획은 아주 좋은, 사용할 수 단지 아닙니다!

네 --set NX 전직의 진화의 역사

위의 프로그램에 주로 안 좋은 setnx / SETEX는 다음 두 명령을 함께하지 라인에 아직 이전의 클라이언트 연결 해제 문제의 성공 후 해결할 수없는, 두 개의 분리 된 명령입니다입니까?

예, 레디 스의 문제도 알고 공식, 그래서 2.6.12 버전은 명령에 추가 일부 매개 변수를 설정합니다 :

SET key value [EX seconds] [PX milliseconds] [NX|XX]

초 EX, 만료 시간,

밀리 초 단위 PX, 만료 시간,

설정하기 전에 어떤 성공이없는 경우 NX는 존재하지

XX 존재 존재? 성공적으로이 설정되어있는 경우

이 순서로 우리는 더 이상 공공 호에서 분리 아무 이유없이 클라이언트 두려워하지 않습니다 [이 기사 원래 "통 형제 소스 읽기"].

반복

따라서, 옵션 IV의 사용이 처음 인 set lock_user_1 value nx ex 30로크를 획득하기 위해 로크를 획득 한 후 사용하여 최종 완성 사용 del lock_user_1로크의 방출.

그러나,이 솔루션은 아무 문제가 없다?

물론 거기, 잠금 단순히 해제 수행되는 사실에 del lock_user_1, 그리고 잠금을 확인하지 않습니다 취득한 현재 클라이언트가 아닙니다.

따라서이 방식은 완벽하지 않습니다.

오 개 --random 값 +의 ​​루아 스크립트의 진화 역사

위의 방식은 잠금 제어의 방출이없는 장소에서, 다음 클라이언트 함께 잠겨 있어야 잠금 스레드와 스레드의 방출을 제어하는 ​​다른 방법이없는 주로 인해, 여기에 완벽하지?

주어진 레디 스 공식 프로그램이 있습니다 :

 // 加锁
 SET resource_name my_random_value NX PX 30000
 
 // 释放锁
 if redis.call("get",KEYS[1]) == ARGV[1] then
     return redis.call("del",KEYS[1])
 else
     return 0
 end

잠긴 상태에서만 현재의 클라이언트 임의의 값이 알 수 있도록 임의의 값을 설정합니다.

잠금이 해제 될 때, 루아 스크립트의 구현은,이 후 잠금을 해제하거나 잠금 실패를 해제 직접 돌아 델 실행하면 임의의 값이 위에서 설정되지 않 잠금 해당 값을 확인 첫째, 완전한 명령으로이 루아 스크립트를 넣어 .

우리는 레디 스 단일 스레드, 그래서이 루아 스크립트와 델은 동시성 문제를받지 않습니다,하지만이 두 명령은 문제의이 복잡 할 것이다 것 같은 루아 스크립트 오히려, 처음에 델 자바를 얻을 수없는, 알고 다음 명령 레디 스 함께 전송된다.

이것은 어떻게 만료 시간에 훨씬 더 설정되어,이 프로그램은 비교적 완벽하지만, 약간의 결함이 있습니까?

너무 작게 설정, 논리 내에서 실행을 종료하지 않는 스레드를 잠글 가능성이 있으며, 잠금 자동 잠금을 획득 할 수있는 또 다른 스레드의 결과 발표 및 동시성 문제가 가지고있다;

너무 크게 설정이 연결 클라이언트를 고려할 필요가있다 잠금이 오래 기다려야합니다.

그래서, 여기에 우리가 새로운 문제로 파생 된 시간의 만료 나는 조금 작은 설정하지만, 자동으로 곧 만료 갱신 할 수 있습니다.

여섯 --redisson의 진화 역사 (redis2.8의 +)

당신은 또한 청취자의 갱신을 처리하기 위해 자신의 스레드를 시작할 수 있지만, 위의 시나리오에서 결함이 만료 시간을 잘 이해 아니라, 코드가 아주 좋은 쓰기 아니라, 좋은 기성품 바퀴 redisson 우리는, 우리가 그럼이 논리를 달성하기 위해 도움이되었습니다 그것의 직접 사용을 인수합니다.

또한, 다양한 문제의 진화의 전체 계정이 왼쪽 레디 스 redisson, 만하면, 그것은 보초에서 진화 독립 실행 형 클러스터 또는 클러스터로 진화 여부, 모든 것이 잘 처리 모드, 센티넬 모드, 클러스터 모델, 독립형 간단한 구성의 변경은에 코드를 변경하지 마십시오, 당신은 말할 수있는 비 (산업) 자주 (커뮤니티) 자 (좋은) 것 (심장).

내부 분산 잠금 redisson는 알고리즘 공식 추천 Redlock 알고리즘을 사용하여 구현.

또한, redisson는 분산 객체의 수 (분산 클래스 원자), 분산 세트 (분산지도 / 목록 / 설정 / 대기열 등), 싱크로 분산 (분산 해, CountDownLatch / 세마포어 등)의 분포를 제공합니다 유형 잠금 (분산 잠금 공정 / 불공정 잠금 / 읽기 - 쓰기 잠금 등), 우리는 가서 볼 수있는 관심, 다음 링크를 게시되어 있습니다 :

반복

Redlock 설명 : HTTPS : //redis.io/topics/distlock

redisson 설명 : HTTPS : //github.com/redisson/redisson/wiki

코드 구현

이전 5 개 디자인이 구식이기 때문에, 동양 형제 여기 게으른 때문에, 그들을 우리는 지난 redisson의 직접적인 구현을 참조 구현되지 않습니다.

의 pom.xml 파일

springboot 2.1.6 버전, 다음에 그들의주의의 springboot 1.x의 버전은 여기에 사용, 스프링 레디 스를 추가하고 redisson 의존하고, 방법을 찾을 수 있습니다 github에 위의 내용을 참조하십시오.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-data-21</artifactId>
    <version>3.11.0</version>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.11.0</version>
</dependency>

application.yml 파일

레디 스 연결 구성 정보, 통 동생이 여기에 세 가지 방법을 제공.

spring:
  redis:
    # 单机模式
    #host: 192.168.1.102
    #port: 6379
    # password: <your passowrd>
    timeout: 6000ms  # 连接超时时长(毫秒)
    # 哨兵模式 【本篇文章由公众号“彤哥读源码”原创】
#    sentinel:
#      master: <your master>
#      nodes: 192.168.1.101:6379,192.168.1.102:6379,192.168.1.103:6379
    # 集群模式(三主三从伪集群)
    cluster:
      nodes:
        - 192.168.1.102:30001
        - 192.168.1.102:30002
        - 192.168.1.102:30003
        - 192.168.1.102:30004
        - 192.168.1.102:30005
        - 192.168.1.102:30006

로커 인터페이스

로커 정의 된 인터페이스.

public interface Locker {
    void lock(String key, Runnable command);
}

RedisLocker 구현 클래스

우리가 그것을 달성하는 방법은 바로 뒤에이 개별적으로 RedissonClient 콩을 구성 할 필요가 없다는 잠금 주를 취득 RedissonClient, redisson 프레임 워크 인스턴스 자동 구성에 따라 생성 RedissonClient.

@Component
public class RedisLocker implements Locker {

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void lock(String key, Runnable command) {
        RLock lock = redissonClient.getLock(key);
        try {
            // 【本篇文章由公众号“彤哥读源码”原创】
            lock.lock();
            command.run();
        } finally {
            lock.unlock();
        }
    }
}

테스트 카테고리

1000 개 스레드, 각 내부 인쇄 단어를 시작한 다음 1 초 잔다.


@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class RedisLockerTest {

    @Autowired
    private Locker locker;

    @Test
    public void testRedisLocker() throws IOException {
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                locker.lock("lock", ()-> {
                    // 可重入锁测试
                    locker.lock("lock", ()-> {
                        System.out.println(String.format("time: %d, threadName: %s", System.currentTimeMillis(), Thread.currentThread().getName()));
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    });
                });
            }, "Thread-"+i).start();
        }

        System.in.read();
    }
}

결과 :

안정적으로 단어를 인쇄 1000MS 주위에서 볼 수있는, 설명 된 잠금이 가능하며 재진입입니다.

time: 1570100167046, threadName: Thread-756
time: 1570100168067, threadName: Thread-670
time: 1570100169080, threadName: Thread-949
time: 1570100170093, threadName: Thread-721
time: 1570100171106, threadName: Thread-937
time: 1570100172124, threadName: Thread-796
time: 1570100173134, threadName: Thread-944
time: 1570100174142, threadName: Thread-974
time: 1570100175167, threadName: Thread-462
time: 1570100176180, threadName: Thread-407
time: 1570100177194, threadName: Thread-983
time: 1570100178206, threadName: Thread-982
...

RedissonAutoConfiguration

) 주로 (redisson을보고, 우리는 단순히 소스 코드를 보면, 그냥 사실, 그것은 자동으로 구성되는 것을 RedissonAutoConfiguration을 구성하려면이 방법을 필요로하지 RedissonClient :


@Configuration
@ConditionalOnClass({Redisson.class, RedisOperations.class})
@AutoConfigureBefore(RedisAutoConfiguration.class)
@EnableConfigurationProperties({RedissonProperties.class, RedisProperties.class})
public class RedissonAutoConfiguration {

    @Autowired
    private RedissonProperties redissonProperties;
    
    @Autowired
    private RedisProperties redisProperties;
    
    @Autowired
    private ApplicationContext ctx;
    
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(RedisConnectionFactory.class)
    public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) {
        return new RedissonConnectionFactory(redisson);
    }
    
    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean(RedissonClient.class)
    public RedissonClient redisson() throws IOException {
        Config config = null;
        Method clusterMethod = ReflectionUtils.findMethod(RedisProperties.class, "getCluster");
        Method timeoutMethod = ReflectionUtils.findMethod(RedisProperties.class, "getTimeout");
        Object timeoutValue = ReflectionUtils.invokeMethod(timeoutMethod, redisProperties);
        int timeout;
        if(null == timeoutValue){
            // 超时未设置则为0
            timeout = 0;
        }else if (!(timeoutValue instanceof Integer)) {
            // 转毫秒
            Method millisMethod = ReflectionUtils.findMethod(timeoutValue.getClass(), "toMillis");
            timeout = ((Long) ReflectionUtils.invokeMethod(millisMethod, timeoutValue)).intValue();
        } else {
            timeout = (Integer)timeoutValue;
        }
        
        // 看下是否给redisson单独写了一个配置文件
        if (redissonProperties.getConfig() != null) {
            try {
                InputStream is = getConfigStream();
                config = Config.fromJSON(is);
            } catch (IOException e) {
                // trying next format
                try {
                    InputStream is = getConfigStream();
                    config = Config.fromYAML(is);
                } catch (IOException e1) {
                    throw new IllegalArgumentException("Can't parse config", e1);
                }
            }
        } else if (redisProperties.getSentinel() != null) {
            // 如果是哨兵模式
            Method nodesMethod = ReflectionUtils.findMethod(Sentinel.class, "getNodes");
            Object nodesValue = ReflectionUtils.invokeMethod(nodesMethod, redisProperties.getSentinel());
            
            String[] nodes;
            // 看sentinel.nodes这个节点是列表配置还是逗号隔开的配置
            if (nodesValue instanceof String) {
                nodes = convert(Arrays.asList(((String)nodesValue).split(",")));
            } else {
                nodes = convert((List<String>)nodesValue);
            }
            
            // 生成哨兵模式的配置
            config = new Config();
            config.useSentinelServers()
                .setMasterName(redisProperties.getSentinel().getMaster())
                .addSentinelAddress(nodes)
                .setDatabase(redisProperties.getDatabase())
                .setConnectTimeout(timeout)
                .setPassword(redisProperties.getPassword());
        } else if (clusterMethod != null && ReflectionUtils.invokeMethod(clusterMethod, redisProperties) != null) {
            // 如果是集群模式
            Object clusterObject = ReflectionUtils.invokeMethod(clusterMethod, redisProperties);
            Method nodesMethod = ReflectionUtils.findMethod(clusterObject.getClass(), "getNodes");
            // 集群模式的cluster.nodes是列表配置
            List<String> nodesObject = (List) ReflectionUtils.invokeMethod(nodesMethod, clusterObject);
            
            String[] nodes = convert(nodesObject);
            
            // 生成集群模式的配置
            config = new Config();
            config.useClusterServers()
                .addNodeAddress(nodes)
                .setConnectTimeout(timeout)
                .setPassword(redisProperties.getPassword());
        } else {
            // 单机模式的配置
            config = new Config();
            String prefix = "redis://";
            Method method = ReflectionUtils.findMethod(RedisProperties.class, "isSsl");
            // 判断是否走ssl
            if (method != null && (Boolean)ReflectionUtils.invokeMethod(method, redisProperties)) {
                prefix = "rediss://";
            }
            
            // 生成单机模式的配置
            config.useSingleServer()
                .setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
                .setConnectTimeout(timeout)
                .setDatabase(redisProperties.getDatabase())
                .setPassword(redisProperties.getPassword());
        }
        
        return Redisson.create(config);
    }

    private String[] convert(List<String> nodesObject) {
        // 将哨兵或集群模式的nodes转换成标准配置
        List<String> nodes = new ArrayList<String>(nodesObject.size());
        for (String node : nodesObject) {
            if (!node.startsWith("redis://") && !node.startsWith("rediss://")) {
                nodes.add("redis://" + node);
            } else {
                nodes.add(node);
            }
        }
        return nodes.toArray(new String[nodes.size()]);
    }

    private InputStream getConfigStream() throws IOException {
        // 读取redisson配置文件
        Resource resource = ctx.getResource(redissonProperties.getConfig());
        InputStream is = resource.getInputStream();
        return is;
    }

    
}

인터넷에서 찾은 정보 구성의 대부분은 (버전은 문제가 될 수 있음) 중복, 소스 코드를 보면이 소스의 장점을 살펴이며, 매우 분명하다.

개요

(1) 역사적 이유로 인해 레디 스, 세 가지 모드가 있습니다 : 하나, 센티넬, 클러스터는;

진화의 역사 (2) 분산 잠금 레디 스 구현 : 설정 -> setnx -> setnx + SETEX -> 설정 NX 전 (또는 PX) -> 설정 NX 전 (또는 PX) + 루아 스크립트 -> redisson;

(3) 레디 스는 기존의 분산 로크 휠 redisson가 사용될 수있다;

(4) redisson 또한 분산, 분산 동기화 분산 객체의 컬렉션으로서 유용한 성분을 제공하고;

달걀

분산 잠금 레디 스 장점은 무엇입니까?

A : 1) 대부분의 시스템 캐시로 레디 스에 의존, 추가는) 상대 사육사를 위해 (다른 구성 요소에 의존하지;

2) 레디 스는보다 안정 싱글 포인트 MySQL의에 대해, 배포 클러스터 수;

3) MySQL의 연결이 mysql을의 압력을 증가하지 않습니다 차지하지 않습니다;

4) 레디 스 커뮤니티 비교적 활성 redisson보다 안정적이고 신뢰성을 달성하는 단계;

5) 오래된기구의 사용은 아니지만 적시에, 클라이언트가 파손되는 문제를 해결하는 단계;

6) 사용할 수 있습니다 redisson 기성 바퀴가 있습니다, 잠금 유형은 상대적으로 완료;

레디 스는 단점이 무엇 잠금을 분산?

A는 : 1) 클러스터 모드를 모두 마스터 노드 잠금 명령에서 구현 될 것이라고 (2N + 1)의 대부분이 잠금이 성공 얻으려면 더 많은 노드 잠금의 느린 과정;

2) 높은 동시성에서, 스레드가 동일한 잠금 경쟁이 치열한 경우,이 시스템 자원을 많이 차지합니다, 시도 잠금 잠을 취득하지 않을 것이다;

3) 구덩이 역사의 많은 원인이 어려운 분산 강력한 레디 스를 달성하기 위해 잠글;

즉, 분산 잠금 레디 스의 장점은 우리의 대부분은 이유 레디 스 분산 잠금 장치로 시스템을 사용하다, 단점, 지역 사회 활동가를보다 큽니다.

추천 도서

1, 오프닝 시리즈의 Sike 자바 동기화

2, 분석 마법의 Sike 안전하지 않은 자바 클래스

3, JMM의 Sike 자바 동기화 시리즈 (자바 메모리 모델)

4, 휘발성 해결의 Sike 자바 동기화 시리즈

5, 동기화 해결의 Sike 자바 동기화 시리즈

6, 동기의 Sike 자바 시리즈는 자신을 잠금 잠금 쓰기

7, AQS 기사의 Sike 자바 동기화 시리즈

8 의 Sike ReentrantLock와 해결의 자바 소스 코드를 동기화 시리즈의 (a) - 공정 잠금, 불공정 잠금

9, 조건 잠금 - (2 개)의 Sike ReentrantLock와 해결의 자바 소스 코드를 동기화 시리즈

10 ReentrantLock와 VS의 Sike 자바 동기화 시리즈는 동기화

11 Sike ReentrantReadWriteLock 파싱 Java 소스 동기식 직렬

12 Sike 세마포 동기화 자바 소스 분석 시리즈

13 Sike 자바 소스 동기식 직렬 CountDownLatch를 파싱

14, AQS 마지막 장의 Sike 자바 동기화 시리즈

15 Sike StampedLock 파싱 Java 소스 동기식 직렬

16 Sike으로 CyclicBarrier 파싱 Java 소스 동기식 직렬

17 분석 페이저 Sike Java 소스 코드 동기화 시리즈

18 동기화 분산 잠금 mysql을의 Sike 자바 시리즈

19 Sike 자바 동기화 사육사 시리즈 분산 잠금


나는 대중 번호의 관심을 환영보기 소스 코드를 더 시리즈, 바다의 형제 통 소스 "통 형제 소스 읽기는"함께 수영.

QR 코드

추천

출처www.cnblogs.com/tong-yuan/p/11621361.html