Java 심층 학습(5): 잠금
간단하게 말하면 중복 잠금 지원, 중용성
특징: 자물쇠 전달 가능, 방법 귀속 전달
목적: 잠금 현상을 피했다
코드:
public class Test implements Runnable {
@Override
public void run() {
method1();
}
public synchronized void method1() {
System.out.println("method1");
method2();
}
public synchronized void method2() {
System.out.println("method2");
}
public static void main(String[] args) {
new Thread(new Test()).start();
}
}
인쇄:
method1
method2
분석: 만약 자물쇠를 다시 사용할 수 없다면 여기에 사라진 자물쇠 문제가 발생할 것이다
ReentrantLock 잠금 사용:
public class TestLock implements Runnable {
//
private Lock reentrantLock = new ReentrantLock();
@Override
public void run() {
method1();
}
public void method1() {
try {
reentrantLock.lock();
System.out.println("method1");
method2();
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
public void method2() {
try {
reentrantLock.lock();
System.out.println("method2");
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
public static void main(String[] args) {
new Thread(new TestLock()).start();
}
}
읽기 및 쓰기 잠금:
높은 병발 시 쓰기 작업과 동시에 읽기 작업을 허용하지 않아야 한다
(다중 스레드 중: 읽기-읽기 공존, 읽기-쓰기-쓰기 공존 불가)
코드:읽기 및 쓰기 작업의 라인 보안 문제 만들기
public class TestWriteLock {
Map cache = new HashMap<>();
//
public void put(String key, String value) {
try {
System.out.println(" key : " + key + " value : " + value);
Thread.sleep(50);
cache.put(key, value);
System.out.println(" key : " + key + " value : " + value);
} catch (Exception e) {
e.printStackTrace();
}
}
//
public String get(String key) {
System.out.println(" key : " + key);
String value = cache.get(key);
System.out.println(" key : " + key + " value : " + value);
return value;
}
public static void main(String[] args) {
TestWriteLock test = new TestWriteLock();
Thread readThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
test.put("i", i + "");
}
}
});
Thread writeThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
test.get("i");
}
}
});
readThread.start();
writeThread.start();
}
}
관찰 인쇄: 불합리한 발견
key : i value : 0
key : i
key : i value : null
.................................
분석: 쓰기가 완료되지 않았을 때 읽기 시작했는데 결과는 비어 있습니다
해결 방법:
1.synchronized를 사용하면 해결할 수 있지만 효율이 낮아 쓰기를 동시에 읽을 수 없어 막힌다
2. 읽기 및 쓰기 자물쇠 사용
public class TestWriteLock {
Map cache = new HashMap<>();
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
//
public void put(String key, String value) {
try {
writeLock.lock();
System.out.println(" key : " + key + " value : " + value);
Thread.sleep(50);
cache.put(key, value);
System.out.println(" key : " + key + " value : " + value);
} catch (Exception e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
//
public String get(String key) {
String value = "";
try {
readLock.lock();
System.out.println(" key : " + key);
value = cache.get(key);
System.out.println(" key : " + key + " value : " + value);
} catch (Exception e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
return value;
}
public static void main(String[] args) {
TestWriteLock test = new TestWriteLock();
Thread readThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
test.put("i", i + "");
}
}
});
Thread writeThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
test.get("i");
}
}
});
readThread.start();
writeThread.start();
}
}
관찰 인쇄: 완벽한 해결
낙관적 잠금:
쉽게 말하면 낙관적 자물쇠는 자물쇠가 없고 막히지 않고 기다림이 없다
SQL 문의 예:
UPDATE TABLE SET X=X+1,VERSION=VERSION+1 WHERE ID=#{id} AND VERSION=#{version}
높은 병발지 상황에서 초기 버젼이 1이라고 가정하면 요청 1이 오고 id와 버젼에 따라 찾을 수 있기 때문에 업데이트를 허용합니다
요청 2가 동시에 작동하지만 id와version에 따라 찾을 수 없습니다 (요청 1이 수정되었습니다). 업데이트를 허용하지 않습니다.
비관 잠금:
간단하게 말하면 웨이트급 자물쇠가 막히고 기다린다
자물쇠를 잠근 후 한 라인만 조작할 수 있다는 뜻으로 이해할 수 있다. 즉 자바의synchronized이다
원자 클래스:
아날로그 스레드 보안 문제의 코드:
public class ThreadTest implements Runnable {
private static int count = 1;
@Override
public void run() {
while (true) {
Integer count = getCount();
if (count >= 100) {
break;
}
System.out.println(count);
}
}
public synchronized Integer getCount() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return count++;
}
public static void main(String[] args) {
ThreadTest t = new ThreadTest();
new Thread(t).start();
new Thread(t).start();
}
}
인쇄를 관찰한 결과 과연 라인 안전에 문제가 생겼음을 발견하였다
일종의 수정 방식: 효율이 비교적 낮다
public synchronized Integer getCount() {
원자류 사용: 낙관적 잠금, 밑바닥 잠금 없음, CAS 잠금 없음 기술 사용
public class ThreadTest implements Runnable {
//
private AtomicInteger atomicInteger = new AtomicInteger();
@Override
public void run() {
while (true) {
Integer count = getCount();
if (count >= 100) {
break;
}
System.out.println(count);
}
}
public Integer getCount() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return atomicInteger.incrementAndGet();
}
public static void main(String[] args) {
ThreadTest t = new ThreadTest();
new Thread(t).start();
new Thread(t).start();
}
}
CAS 잠금 해제 기술(Compare And Swap):
비교 재교환
로컬 메모리에 공유 메모리의 복사본 저장
예를 들어 메인 메모리에 i=0이 있으면 두 라인의 로컬 메모리로 복사합니다
두 라인이 i++를 실행하면 로컬 메모리가 i=1로 바뀌고 메인 메모리로 리셋됩니다
CAS 알고리즘:
CAS(V, E, N) 매개변수는 다음과 같습니다.
V는 업데이트할 변수(기본 메모리)를 나타냅니다.
E는 예상 값(로컬 메모리)을 나타냅니다.
N은 새 값(새 값)을 나타냅니다.
V 값이 E 값인 경우에만 (기본 메모리=로컬 메모리) V 값을 N으로 설정합니다.
만약 V 값과 E 값이 다르다면 (메인 메모리! = 로컬 메모리), 이미 다른 라인이 업데이트되었음을 의미하며, 현재 라인은 아무것도 하지 않습니다.
마지막으로 CAS는 현재 V의 실제 값을 반환합니다.
원자류의 원본 코드 관찰하기
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
//
int current = get();
//
int next = current + 1;
// Native compareAndSet, CAS
if (compareAndSet(current, next))
// ,
return next;
}
}
CAS 잠금 해제 메커니즘의 단점:
1. 데드사이클
2. ABA 문제: 변수 V가 처음 읽을 때 A이고 값을 부여하려고 준비할 때 A인 것을 검사하면 값이 다른 라인에서 수정되지 않았음을 설명할 수 있습니까
(이 기간 동안 B로 변경된 후에 A로 다시 바뀌면 CAS 작업은 수정된 적이 없다고 오해할 수 있습니다. 이 경우 자바 패키지는 표시된 원자 인용류인 Atomic Stamped Reference를 제공합니다. 변수 값을 제어하는 버전을 통해 CAS의 정확성을 확보할 수 있습니다.)
JVM 데이터 동기화: 분산 잠금
자전거 자물쇠와 상호 배척 자물쇠의 차이점:
비관과 낙관 자물쇠의 차이, 자전거 자물쇠는 사순환으로 막히지 않으며, 상호 배척 자물쇠는 같은 시간에 한 라인만 데이터에 접근한다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.