java 병렬 프로 그래 밍 테마(5)---상세(JUC)ReentrantLock
16837 단어 자바병렬 프로 그래 밍jucReentrantLock
1.ReentrantLock 안내
단어의 뜻 으로 우 리 는 이것 이 다시 들 어 갈 수 있다 는 뜻 이라는 것 을 알 수 있다.그렇다면 다시 들 어 갈 수 있 는 것 은 자물쇠 에 무엇 을 의미 하 는 것 일 까?쉽게 말 하면 자물쇠 와 관련 된 획득 카운터 가 있 습 니 다.자물쇠 가 있 는 한 스 레 드 가 다시 잠 겨 있 으 면 계수 기 를 얻 으 려 면 1 을 추가 하고 자 물 쇠 는 두 번 풀 려 야 진정 으로 풀 릴 수 있 습 니 다.이것 은 synchronized 의 의 미 를 모방 했다.스 레 드 가 이미 가지 고 있 는 모니터 가 보호 하 는 synchronized 블록 에 들 어가 면 스 레 드 가 계속 진행 할 수 있 습 니 다.스 레 드 가 두 번 째(또는 후속)synchronized 블록 을 종료 할 때 자 물 쇠 를 풀 지 않 고 스 레 드 가 들 어 오 는 모니터 가 보호 하 는 첫 번 째 synchronized 블록 을 종료 할 때 만 자 물 쇠 를 풀 수 있 습 니 다.
1.1 공평 자물쇠 와 불공평 자물쇠
ReentrantLock 의 소스 코드 를 보면 무 참 구조 함수 가 이 렇 습 니 다.
public ReentrantLock() {
sync = new NonfairSync();
}
NonfairSync()방법 은 불공평 한 자물쇠 의 실현 방법 이 고 Reentrantlock 은 참조 가 있 는 구조 방법 이 있 습 니 다.
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
공평(fair)자 물 쇠 를 원 하 는 지,불공평(unfair)자 물 쇠 를 원 하 는 지 선택 할 수 있 습 니 다.공정 한 자 물 쇠 는 스 레 드 를 요청 한 순서대로 자 물 쇠 를 얻 게 합 니 다.불공평 한 자 물 쇠 는 자 물 쇠 를 직접 가 져 올 수 있 습 니 다.이 경우 스 레 드 는 먼저 자 물 쇠 를 요청 한 다른 스 레 드 보다 먼저 자 물 쇠 를 가 져 올 수 있 습 니 다.왜 우 리 는 모든 자물쇠 가 공평 하지 못 하 게 합 니까?어쨌든 공평 은 좋 은 일이 고 불공평 은 좋 지 않 은 것 이지 않 습 니까?아이들 이 결정 을 내 리 려 고 할 때,항상"이 건 불공평 해"라 고 외친다.우 리 는 공평 이 매우 중요 하 다 고 생각한다.아이들 도 안다.)현실 에서 자 물 쇠 는 매우 건장 한 자물쇠 로 매우 큰 성능 비용 을 보장 한다.공평 을 확보 하기 위해 필요 한 기장(bookeeping)과 동기 화 를 확보 하려 면 불공 정 자물쇠 의 삼투 율 보다 쟁탈 당 하 는 공평 자물쇠 가 더 낮 다 는 뜻 이다.기본 설정 으로서 공정 을 false 로 설정 해 야 합 니 다.공정 이 알고리즘 에 중요 하지 않 으 면 스 레 드 에 따라 줄 을 서 는 순서 로 서 비 스 를 해 야 합 니 다.
다음은 우리 가 먼저 예 를 하나 보 자.
public class TestReentrantLock implements Runnable{
ReentrantLock lock = new ReentrantLock();
public void get() {
lock.lock();
System.out.println(Thread.currentThread().getId());
set();
lock.unlock();
}
public void set() {
lock.lock();
System.out.println(Thread.currentThread().getId());
lock.unlock();
}
@Override
public void run() {
get();
}
public static void main(String[] args) {
TestReentrantLock ss = new TestReentrantLock();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}
실행 결과:10
10
12
12
11
11
Process finished with exit code 0
결과적으로 우 리 는 같은 라인 이 같은 ReentrantLock 자물쇠 에 두 번 들 어 갔 음 을 알 수 있다.
2.condition 조건 변수
루트 Object 는 스 레 드 의 wait(),notify(),notify All()사이 에서 통신 하 는 특수 한 방법 을 포함 하고 있다 는 것 을 알 고 있 습 니 다.대상 에 wait 나 notify 를 위해 서 는 대상 의 자 물 쇠 를 가지 고 있어 야 합 니 다.Lock 이 동기 화 된 요약 인 것 처럼 Lock 프레임 워 크 는 wait 와 notify 에 대한 요약 을 포함 하 는데 이 요약 을 조건(Condition)이 라 고 한다.Condition 의 방법 은 wait,notify,notify All 방법 과 유사 하 며,각각 await,signal,signal All 이 라 고 명명 되 어 있 으 며,Object 의 대응 방법 을 덮어 쓸 수 없 기 때 문 입 니 다.
우선 우리 가 한 문 제 를 계산 해 보 자.
우 리 는 1 부터 9 까지 9 개의 숫자 를 인쇄 하려 고 합 니 다.A 스 레 드 에서 1,2,3 을 먼저 인쇄 한 다음 에 B 스 레 드 에서 4,5,6 을 인쇄 한 다음 에 A 스 레 드 에서 7,8,9 를 인쇄 합 니 다.이 문 제 는 여러 가지 해법 이 있 습 니 다.우 리 는 먼저 Object 의 wait,notify 방법 으로 이 루어 집 니 다.
public class WaitNotifyDemo {
private volatile int val = 1;
private synchronized void printAndIncrease() {
System.out.println(Thread.currentThread().getName() +"prints " + val);
val++;
}
// print 1,2,3 7,8,9
public class PrinterA implements Runnable {
@Override
public void run() {
while (val <= 3) {
printAndIncrease();
}
// print 1,2,3 then notify printerB
synchronized (WaitNotifyDemo.this) {
System.out.println("PrinterA printed 1,2,3; notify PrinterB");
WaitNotifyDemo.this.notify();
}
try {
while (val <= 6) {
synchronized (WaitNotifyDemo.this) {
System.out.println("wait in printerA");
WaitNotifyDemo.this.wait();
}
}
System.out.println("wait end printerA");
} catch (InterruptedException e) {
e.printStackTrace();
}
while (val <= 9) {
printAndIncrease();
}
System.out.println("PrinterA exits");
}
}
// print 4,5,6 after printA print 1,2,3
public class PrinterB implements Runnable {
@Override
public void run() {
while (val < 3) {
synchronized (WaitNotifyDemo.this) {
try {
System.out
.println("printerB wait for printerA printed 1,2,3");
WaitNotifyDemo.this.wait();
System.out
.println("printerB waited for printerA printed 1,2,3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
while (val <= 6) {
printAndIncrease();
}
System.out.println("notify in printerB");
synchronized (WaitNotifyDemo.this) {
WaitNotifyDemo.this.notify();
}
System.out.println("notify end printerB");
System.out.println("PrinterB exits.");
}
}
public static void main(String[] args) {
WaitNotifyDemo demo = new WaitNotifyDemo();
demo.doPrint();
}
private void doPrint() {
PrinterA pa = new PrinterA();
PrinterB pb = new PrinterB();
Thread a = new Thread(pa);
a.setName("printerA");
Thread b = new Thread(pb);
b.setName("printerB");
// b , b , wait, a , notify
b.start();
a.start();
}
}
실행 결 과 는:printerB wait for printerA printed 1,2,3
printerA prints 1
printerA prints 2
printerA prints 3
PrinterA printed 1,2,3; notify PrinterB
wait in printerA
printerB waited for printerA printed 1,2,3
printerB prints 4
printerB prints 5
printerB prints 6
notify in printerB
notify end printerB
wait end printerA
printerA prints 7
printerA prints 8
printerA prints 9
PrinterA exits
PrinterB exits.
Process finished with exit code 0
우 리 는 위의 절 차 를 분석 해 보 자.
먼저 main 방법 에서 우 리 는 B 스 레 드 를 먼저 시작 한 것 을 보 았 습 니 다.B 스 레 드 는 wait()대상 을 가지 고 있 고 A 스 레 드 는 notify()를 가지 고 있 기 때문에 A 를 먼저 시작 하면 잠 금 상태 가 될 수 있 습 니 다.
B 라인 시작 후 run()에 들 어 가 는 방법:
while (val < 3) {
synchronized (WaitNotifyDemo.this) {
try {
System.out.println("printerB wait for printerA printed 1,2,3");
WaitNotifyDemo.this.wait();
System.out.println("printerB waited for printerA printed 1,2,3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
while (val <= 6) {
printAndIncrease();
}
여기에 while 순환 이 있 습 니 다.val 의 값 이 3 보다 작 으 면 Wait Notify Demo 의 인 스 턴 스 동기 화 블록 에서 Wait Notify Demo.this.wait()방법 을 호출 합 니 다.wait 든 notify 든 notify,notify All 방법 은 인 스 턴 스 대상 의 동기 화 블록 에서 실행 되 어야 현재 스 레 드 가 동기 화 인 스 턴 스 의 동기 화 제어 권 을 얻 을 수 있 습 니 다.동기 블록 에서 wait 나 notify 방법 을 실행 하지 않 으 면 자바.lang.IllegalMonitor State Exception 이상 이 발생 합 니 다.또한 wait 방법 양쪽 의 동기 화 블록 은 wait 실행 이 끝 난 후에 대상 자 물 쇠 를 풀 수 있 음 을 주의해 야 합 니 다.이렇게 PrinterB 는 대기 상태 에 들 어 갔 습 니 다.PrinterA 의 run 방법 을 살 펴 보 겠 습 니 다.
while (val <= 3) {
printAndIncrease();
}
// print 1,2,3 then notify printerB
synchronized (WaitNotifyDemo.this) {
System.out.println("PrinterA printed 1,2,3; notify PrinterB");
WaitNotifyDemo.this.notify();
}
try {
while (val <= 6) {
synchronized (WaitNotifyDemo.this) {
System.out.println("wait in printerA");
WaitNotifyDemo.this.wait();
}
}
System.out.println("wait end printerA");
} catch (InterruptedException e) {
e.printStackTrace();
}
여기 서 먼저 1,2,3 을 인쇄 한 다음 동기 블록 에서 Wait Notify Demo 인 스 턴 스 의 notify 방법 을 호출 했 습 니 다.그러면 PrinterB 는 계속 실행 하 라 는 통 지 를 받 은 다음 PrinterA 는 대기 상태 에 들 어가 PrinterB 통 지 를 기다 리 고 있 습 니 다.PrinterB run 방법 나머지 코드 를 다시 보 겠 습 니 다.
while (val <= 6) {
printAndIncrease();
}
System.out.println("notify in printerB");
synchronized (WaitNotifyDemo.this) {
WaitNotifyDemo.this.notify();
}
System.out.println("notify end printerB");
System.out.println("PrinterB exits.");
PrinterB 는 먼저 4,5,6 을 인쇄 한 다음 동기 블록 에서 notify 방법 을 호출 하여 PrinterA 에 게 실행 을 시작 하 라 고 알 렸 다.PrinterA 가 통 지 를 받 은 후 기다 리 는 것 을 멈 추고 나머지 7,8,9 세 개의 숫자 를 인쇄 합 니 다.다음은 PrinterA run 방법 에 남 은 코드 입 니 다.
while (val <= 9) {
printAndIncrease();
}
전체 프로그램 이 분석 되 었 습 니 다.다음은 Condition 을 사용 하여 이 문 제 를 풀 겠 습 니 다.
public class TestCondition {
static class NumberWrapper {
public int value = 1;
}
public static void main(String[] args) {
//
final Lock lock = new ReentrantLock();
// 3
final Condition reachThreeCondition = lock.newCondition();
// 6
final Condition reachSixCondition = lock.newCondition();
//NumberWrapper , , final
// Integer, Integer
final NumberWrapper num = new NumberWrapper();
// A
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
//
lock.lock();
try {
System.out.println("threadA start write");
//A 3
while (num.value <= 3) {
System.out.println(num.value);
num.value++;
}
// 3 signal, B
reachThreeCondition.signal();
} finally {
lock.unlock();
}
lock.lock();
try {
// 6
reachSixCondition.await();
System.out.println("threadA start write");
//
while (num.value <= 9) {
System.out.println(num.value);
num.value++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lock();
while (num.value <= 3) {
// 3
reachThreeCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
try {
lock.lock();
// , 4,5,6
System.out.println("threadB start write");
while (num.value <= 6) {
System.out.println(num.value);
num.value++;
}
//4,5,6 , A 6
reachSixCondition.signal();
} finally {
lock.unlock();
}
}
});
//
threadB.start();
threadA.start();
}
}
기본 적 인 사고방식 은 먼저 A 라인 이 1,2,3 을 써 야 한 다 는 것 이다.이때 B 라인 은 reach ThredCondition 신 호 를 기 다 려 야 한다.그리고 A 라인 이 3 을 다 쓴 후에 signal 을 통 해 B 라인 에 게'내 가 3 을 썼 으 니 네 차례 야'라 고 알려 야 한다.이때 A 라인 은 Sixcondition 신호 에 도달 할 때 까지 기 다 려 야 한다.이 동시에 B 라인 은 통 지 를 받 고 4,5,6 을 쓰기 시작한다.4,5 를 다 쓰 고 한다.6.그 후에 B 스 레 드 는 A 스 레 드 reach Sixcondition 조건 이 성립 되 었 다 고 알 렸 다.이때 A 스 레 드 는 나머지 7,8,9 를 쓰기 시작 했다.우 리 는 상기 사례 에서 두 개의 Condition 을 만 들 었 고 서로 다른 상황 에서 서로 다른 Condition 을 사용 할 수 있 으 며 wait 와 notify 에 비해 더욱 세밀 한 통 제 를 제공 하 는 것 을 볼 수 있다.
3.스 레 드 차단 원 어 CLockSupport
우 리 는 스 레 드,자물쇠 등 개념 을 거듭 제기 하지만 자 물 쇠 는 실현 된다 면?현재 스 레 드 를 막 고 있 는 또 어떤 대상 인지 어떻게 압 니까?LockSupport 는 JDK 의 하위 클래스 로 자물쇠 와 다른 동기 화 도구 류 의 기본 스 레 드 차단 원 어 를 만 드 는 데 사 용 됩 니 다.
자바 잠 금 및 동기 화 프레임 워 크 의 핵심 AQS:AbstractQueued Synchronizer 는 LockSupport.park()와 LockSupport.unpark()를 호출 하여 스 레 드 의 차단 과 깨 우 는 것 입 니 다.LockSupport 는 이원 신 호 량(1 개의 허가증 만 사용 가능)과 유사 합 니 다.이 허가 가 아직 점용 되 지 않 았 다 면 현재 스 레 드 는 허 가 를 받 고 계속 실 행 됩 니 다.허가 가 점용 되 었 다 면 현재 스 레 드 가 막 혀 허 가 를 기다 리 고 있 습 니 다.
LockSupport 는 특정 스 레 드 를 대상 으로 차단 과 차단 해제 작업 을 합 니 다.Object 의 wait()/notify()/notify All()은 특정 대상 의 대기 집합 을 조작 하 는 데 사 용 됩 니 다.
LockSupport 의 두 가지 주요 방법 은 Park()와 Unpark()입 니 다.그들의 실현 을 살 펴 보 겠 습 니 다.
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
unsafe.park(false, 0L);
setBlocker(t, null);
}
public static void park() {
unsafe.park(false, 0L);
}
public static void unpark(Thread thread) {
if (thread != null)
unsafe.unpark(thread);
}
원본 코드 에서 볼 수 있 듯 이 Park 방법 내부 에서 먼저 현재 스 레 드 를 얻 은 다음 에 현재 스 레 드 를 막 습 니 다.unpark 방법 은 설정 가능 한 스 레 드 로 전송 하여 이 스 레 드 의 잠 금 을 풀 수 있 습 니 다.'스 레 드'를 방법의 매개 변수 로 하여 의미 가 더욱 뚜렷 하고 사용 하기에 도 편리 하 다.한편,wait/notify 의 실현 으로 인해'스 레 드'의 차단/깨 우 는 것 은 스 레 드 자체 에 수 동적 입 니 다.어떤 스 레 드 를 정확하게 제어 하고 언제 차단/깨 우 는 것 이 어렵 습 니 다.무 작위 로 스 레 드(notify)를 깨 우지 않 으 면 모든(notify All)를 깨 우지 않 습 니 다.다음은 예 를 들 어 보 겠 습 니 다.
public class TestLockSupport {
public static Object u = new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super.setName(name);
}
public void run() {
synchronized (u) {
System.out.println("in" + getName());
LockSupport.park();
}
}
}
public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(2000);
t2.start();
LockSupport.unpark(t1);
LockSupport.unpark(t2);
t1.join();
t2.join();
}
}
"LockSupport.unpark(t1);"이 한 마디 가 떨 어 지면 우 리 는 프로그램 이 자물쇠 에 빠 진 것 을 발견 할 것 이다.그리고 재 main 방법 에서 unpark 는 t1 과 t2 가 시 작 된 후에 야 실 행 된 것 을 보 았 습 니 다.그런데 왜 t1 이 시 작 된 후에 t2 도 시 작 된 것 입 니까?주의,**unpark 함 수 는 Park 보다 먼저 호출 할 수 있 습 니 다.예 를 들 어 스 레 드 B 는 unpark 함 수 를 호출 하여 스 레 드 A 에 게'허가'를 보 냈 습 니 다.그러면 스 레 드 A 가 Park 를 호출 할 때'허가'가 있 는 것 을 발견 하면 바로 다시 실행 할 것 입 니 다.**unpark 함 수 는 스 레 드 에'허가(permit)'를 제공 하고 스 레 드 호출 Park 함 수 는'허가'를 기다 리 고 있 습 니 다.이것 은 약간 신 호 량 과 같 지만 이'허가'는 중첩 할 수 없고'허가'는 일회 성 이다.예 를 들 어 스 레 드 B 는 unpark 함 수 를 세 번 연속 으로 호출 했 습 니 다.스 레 드 A 가 Park 함 수 를 호출 하면 이'허가'를 사용 하고 스 레 드 A 가 다시 Park 를 호출 하면 대기 상태 에 들 어 갑 니 다.정시 차단 기능 외 에 중단 영향 도 지원 하지만 다른 수신 중단 함수 와 달리 던 지지 않 습 니 다.
Interrupted Exception 이상,그 는 묵묵히 돌아 올 뿐 이지 만,우 리 는 Thread.Interrupted()등 방법 으로 중단 표 시 를 받 을 수 있 습 니 다.
우 리 는 예 를 하나 보 자.
public class TestLockSupport {
public static Object u = new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super.setName(name);
}
public void run() {
synchronized (u) {
System.out.println("in " + getName());
LockSupport.park();
if (Thread.interrupted()) {
System.out.println(getName() + " !");
}
}
System.out.println(getName() + " ");
}
}
public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(100);
t2.start();
t1.interrupt();
LockSupport.unpark(t2);
}
}
출력:in t1
t1 끊 겼 어!
t1 실행 종료
in t2
t2 실행 종료
Process finished with exit code 0
run 방법 에서 터미널 이상 캡 처 를 통 해 스 레 드 가 중단 되 었 을 때 이상 을 던 지지 않 고 정상적으로 실행 되 는 것 을 볼 수 있 습 니 다.
LockSupport 에 대해 사실은 소개 해 야 할 것 이 많 습 니 다.이런 것들 은 밑바닥 의 일부 방법 을 실 현 했 기 때문에 각종 자물쇠 의 실현 은 모두 이 를 바탕 으로 발 전 된 것 입 니 다.앞으로 jdk 내부 의 차단 체 제 를 한 장 으로 배 울 것 입 니 다.앞에서 우 리 는 Object 의 wait 와 notify,Condition 조건,jdk 에서 외부 에 노출 되 지 않 는 Lock Support 가 원 어 를 막 는 것 에 대해 이야기 했다.그러면 JUC 가방 에 또 다른 차단 체제 인 신 호 량 체제(Semaphore)가 있 으 니 다음 절 에 우리 가 함께 토론 해 보 자.
이상 은 자바 병렬 프로 그래 밍 테마(5)-상세(JUC)ReentrantLock 의 상세 한 내용 입 니 다.자바 ReentrantLock 에 관 한 자 료 는 저희 의 다른 관련 글 을 주목 하 세 요!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Is Eclipse IDE dying?In 2014 the Eclipse IDE is the leading development environment for Java with a market share of approximately 65%. but ac...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.