java 자물쇠의 성능 향상 방법
우리는 자신의 제품이 직면한 문제에 대해 해결 방법을 생각하려고 노력하지만, 이 글에서 저는 여러분께 몇 가지 자주 사용하는 기술을 공유할 것입니다. 예를 들어 자물쇠를 분리하고 데이터 구조를 병행하며 코드가 아닌 데이터를 보호하고 자물쇠의 작용 범위를 좁히는 것입니다. 이 몇 가지 기술은 우리로 하여금 어떠한 도구도 사용하지 않고 자물쇠를 검사하게 할 수 있습니다.
자물쇠는 문제의 근원이 아니라, 자물쇠 간의 경쟁이야말로
일반적으로 다중 스레드의 코드에서 성능 문제에 부딪힐 때, 일반적으로 자물쇠 문제라고 불평한다.자물쇠가 프로그램의 운행 속도와 낮은 확장성을 떨어뜨릴 수 있다는 것은 모두가 알고 있는 사실이다.따라서 이런'상식'을 가지고 코드를 최적화하기 시작하면 그 결과는 나중에 얄미운 병발 문제가 생길 가능성이 높다.
따라서 경쟁 자물쇠와 비경쟁 자물쇠의 차이를 이해하는 것이 중요하다.한 스레드가 다른 스레드가 실행 중인 동기화 블록이나 방법에 들어가려고 할 때 자물쇠 경쟁이 발생합니다.이 스레드는 첫 번째 스레드가 동기화 블록을 실행하고 모니터가 해제될 때까지 대기 상태로 강제로 들어갑니다.같은 시간에 하나의 라인만 동기화된 코드 영역을 실행하려고 시도할 때 자물쇠는 비경쟁 상태를 유지한다.
사실상 경쟁적이지 않은 상황에서 대부분의 응용 프로그램에서 JVM은 동기화에 최적화되었다.비경쟁 자물쇠는 실행 과정에서 어떠한 추가 비용도 가져오지 않는다.따라서 성능 문제로 자물쇠를 원망해서는 안 되고, 자물쇠의 경쟁을 원망해야 한다.이런 인식이 생기면 경쟁의 가능성을 낮추거나 경쟁의 지속 시간을 줄이기 위해 무엇을 할 수 있는지 살펴보자.
코드 대신 데이터 보호
스레드 안전 문제를 해결하는 빠른 방법 중 하나는 전체 방법에 대한 접근성 잠금이다.예를 들어 다음과 같은 방법으로 온라인 포커 게임 서버를 구축하려고 한다.
class GameServer {
public Map<<String, List<Player>> tables = new HashMap<String, List<Player>>();
public synchronized void join(Player player, Table table) {
if (player.getAccountBalance() > table.getLimit()) {
List<Player> tablePlayers = tables.get(table.getId());
if (tablePlayers.size() < 9) {
tablePlayers.add(player);
}
}
}
public synchronized void leave(Player player, Table table) {/*body skipped for brevity*/}
public synchronized void createTable() {/*body skipped for brevity*/}
public synchronized void destroyTable(Table table) {/*body skipped for brevity*/}
}
작가의 의도는 좋다. 새로운 유저가 카드 테이블에 가입할 때, 카드 테이블의 유저 수가 카드 테이블이 수용할 수 있는 유저 총수 9를 초과하지 않도록 확보해야 한다.그러나 이런 해결 방법은 사실상 언제든지 플레이어가 카드 테이블에 들어가는 것을 통제해야 한다. 서버의 방문량이 적을 때도 마찬가지다. 자물쇠가 풀리기를 기다리는 라인은 시스템의 경쟁 사건을 빈번하게 촉발할 것이다.계좌 잔액과 테이블 제한 검사를 포함하는 잠금 블록은 호출 작업의 비용을 대폭 높일 수 있어 경쟁 가능성과 지속 시간을 높일 수 있다.
해결의 첫 번째 단계는 우리가 보호하는 것이 데이터이지 방법 성명에서 방법체로 이동하는 동기화 성명이 아니라는 것을 확보하는 것이다.위의 그 간단한 예에 대해 말하자면, 변화가 크지 않을 것이다.그러나 우리는 단일 join () 방법이 아니라 전체 게임 서비스의 인터페이스에 서서 고려해야 한다.
class GameServer {
public Map<String, List<Player>> tables = new HashMap<String, List<Player>>();
public void join(Player player, Table table) {
synchronized (tables) {
if (player.getAccountBalance() > table.getLimit()) {
List<Player> tablePlayers = tables.get(table.getId());
if (tablePlayers.size() < 9) {
tablePlayers.add(player);
}
}
}
}
public void leave(Player player, Table table) {/* body skipped for brevity */}
public void createTable() {/* body skipped for brevity */}
public void destroyTable(Table table) {/* body skipped for brevity */}
}
원래는 작은 변화일 수도 있지만, 영향은 전체적인 행위 방식이다.유저가 언제든지 카드 테이블에 가입하면 이전의 동기화 방법은 전체 게임 서버 실례를 잠그고 동시에 카드 테이블을 떠나려는 유저와 경쟁하게 됩니다.자물쇠를 방법 성명에서 방법체로 옮기면 자물쇠의 탑재가 늦어져 자물쇠 경쟁의 가능성을 낮출 수 있다.자물쇠의 작용 범위를 축소하다
이제 프로그램이 아닌 데이터를 보호해야 한다고 확신한 후에 필요한 곳에서만 잠금을 설정해야 합니다. 예를 들어 위의 코드가 재구성되면
public class GameServer {
public Map<String, List<Player>> tables = new HashMap<String, List<Player>>();
public void join(Player player, Table table) {
if (player.getAccountBalance() > table.getLimit()) {
synchronized (tables) {
List<Player> tablePlayers = tables.get(table.getId());
if (tablePlayers.size() < 9) {
tablePlayers.add(player);
}
}
}
}
//other methods skipped for brevity
}
이렇게 하면 유저 계정 잔액 검사(IO 조작을 일으킬 수 있음)에 대한 시간 소모 조작을 일으킬 수 있는 코드가 포함되어 자물쇠 제어의 범위 밖으로 옮겨진다.주의, 현재 자물쇠는 단지 유저 수가 책상에 수용할 수 있는 인원수를 초과하는 것을 방지하는 데 사용되며, 계좌 잔액에 대한 검사는 더 이상 이 보호 조치의 일부분이 아니다.분리 자물쇠
너는 위의 예에서 마지막 줄의 코드를 똑똑히 볼 수 있다. 전체 데이터 구조는 같은 자물쇠로 보호되어 있다.이 데이터 구조에 수천 개의 탁자가 있을 수 있음을 감안하면, 우리는 어떤 탁자의 수가 용량을 초과하지 않도록 보호해야 한다. 이런 상황에서 경쟁 사건이 발생할 위험이 여전히 높다.
이것에 관한 간단한 방법은 모든 카드 탁자에 분리 자물쇠를 도입하는 것이다. 아래의 예와 같다.
public class GameServer {
public Map<String, List<Player>> tables = new HashMap<String, List<Player>>();
public void join(Player player, Table table) {
if (player.getAccountBalance() > table.getLimit()) {
List<Player> tablePlayers = tables.get(table.getId());
synchronized (tablePlayers) {
if (tablePlayers.size() < 9) {
tablePlayers.add(player);
}
}
}
}
//other methods skipped for brevity
}
현재 우리는 모든 테이블이 아닌 단일 테이블의 접근성만 동기화하면 자물쇠 경쟁의 가능성을 현저히 낮출 수 있다.구체적인 예를 들어 현재 우리의 데이터 구조에 100개의 테이블 실례가 있다면 현재 경쟁이 발생할 가능성은 이전보다 100배 작을 것이다.안전한 데이터 구조 사용
또 다른 개선할 수 있는 점은 전통적인 단일 스레드 데이터 구조를 버리고 스레드 안전으로 명확하게 설계된 데이터 구조로 바꾸는 것이다.예를 들어 ConcurrentHashMap을 사용하여 카드 테이블의 실례를 저장할 때 코드는 다음과 같을 수 있습니다.
public class GameServer {
public Map<String, List<Player>> tables = new ConcurrentHashMap<String, List<Player>>();
public synchronized void join(Player player, Table table) {/*Method body skipped for brevity*/}
public synchronized void leave(Player player, Table table) {/*Method body skipped for brevity*/}
public synchronized void createTable() {
Table table = new Table();
tables.put(table.getId(), table);
}
public synchronized void destroyTable(Table table) {
tables.remove(table.getId());
}
}
join () 과 leave () 방법 내부의 동기화 블록은 여전히 이전의 예와 같다. 왜냐하면 우리는 단일 테이블 데이터의 완전성을 확보해야 하기 때문이다.Concurrent Hash Map은 이 점에서 아무런 도움이 되지 않습니다.그러나 우리는 여전히 increate Table () 과destory Table () 방법에서 ConcurrentHashMap을 사용하여 새로운 테이블을 만들고 삭제할 것입니다. 이 모든 작업은 ConcurrentHashMap에 있어서 완전히 동기화되어 있습니다. 이것은 우리가 병행하는 방식으로 테이블의 수를 추가하거나 줄일 수 있도록 합니다.기타 조언과 기교
자물쇠의 가시도를 낮추다.위의 예에서 자물쇠는public(대외적으로 볼 수 있음)로 성명되어 있으며, 이로 인해 일부 다른 사람들이 당신이 정성스럽게 설계한 모니터에 자물쇠를 채워서 당신의 일을 파괴할 수 있다.
자바를 보십시오.util.concurrent.locks의 API는 이미 실현된 다른 잠금 정책이 있는지 살펴보고 위의 해결 방안을 개선합니다.
원자 조작을 사용하다.위에서 사용하고 있는 간단한 증가 계수기는 사실상 자물쇠를 요구하지 않는다.위의 예에서는 Integer 대신 Atomic Integer를 계수기로 사용하는 것이 더 적합합니다.
마지막으로 Plumber의 자동 잠금 검사 솔루션을 사용하고 있든 수동으로 라인에서 해결 방법의 정보를 저장하고 있든 이 글이 잠금 경쟁 문제를 해결하는 데 도움이 되기를 바랍니다.
읽어주셔서 감사합니다. 여러분에게 도움이 되었으면 좋겠습니다. 본 사이트에 대한 지지에 감사드립니다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
38. Java의 Leetcode 솔루션텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.