자바 다중 스레드, 자물쇠 메커니즘에 대한 진일보한 분석
1 잠금 재입력 가능
자물쇠를 다시 넣을 수도 있고, 귀속 자물쇠라고도 부른다.그것은 두 가지 의미를 가진다. 첫째, 한 라인이 외층 함수에서 다시 들어갈 수 있는 자물쇠를 얻은 후에 이 함수를 직접 귀속적으로 호출할 수 있고, 둘째, 같은 라인이 외층 함수에서 다시 들어갈 수 있는 자물쇠를 얻은 후에 내층 함수는 이 자물쇠가 다른 코드에 대응하는 제어권을 직접 얻을 수 있다.앞서 언급한 synchronized와 ReentrantLock은 모두 재입력 자물쇠입니다.
ReEnterSyncDemo를 통해java,synchronized 키워드의 재진입성을 보여 드리겠습니다.
1 class SyncReEnter implements Runnable{
2 public synchronized void get(){
3 System.out.print(Thread.currentThread().getId() + "\t");
4 // get set
5 set();
6 }
7 public synchronized void set()
8 {System.out.print(Thread.currentThread().getId()+"\t"); }
9 public void run() //run get
10 { get();}
11 }
12 public class ReEnterSyncDemo {
13 public static void main(String[] args) {
14 SyncReEnter demo=new SyncReEnter();
15 new Thread(demo).start();
16 new Thread(demo).start();
17 }
18 }
첫 번째 줄에서 우리는 syncReEnter 클래스가 Runnable를 실현하는 방식으로 다중 스레드를 실현하도록 하는데 그 중에서 두 번째 줄과 일곱 번째 줄에 정의된 get과 set 방법은 모두synchronized 키워드를 가지고 있다.9줄에 정의된run 방법에서 get 방법을 사용했습니다.main 함수의 15줄과 16줄에서 우리는 2차 라인을 시작했는데, 이 코드의 출력은 다음과 같다.
8 8 9 9
15줄에서 첫 번째 라인을 시작할 때run 방법에서synchronized 키워드를 포함하는 get 방법을 호출합니다. 이때 이 라인은 get 방법의 자물쇠를 얻을 수 있습니다. get의 set 방법을 실행할 때set 방법도synchronized 키워드를 포함하고 set는 get에 포함되기 때문에 set의 자물쇠를 다시 신청할 필요가 없습니다. 계속 실행할 수 있기 때문에 출력을 통해get과 set의 인쇄 문장이 연속적으로 출력되는 것을 보실 수 있습니다.같은 이치로 우리는 16행의 두 번째 가동 라인의 출력을 이해할 수 있다.
ReEnterLock을 통해java, ReentrantLock의 재진입성을 보여 드리겠습니다.
1 import java.util.concurrent.locks.ReentrantLock;
2 class LockReEnter implements Runnable {
3 ReentrantLock lock = new ReentrantLock();
4 public void get() {
5 lock.lock();
6 System.out.print(Thread.currentThread().getId()+"\t");
7 // get set
8 set();
9 lock.unlock();
10 }
11 public void set() {
12 lock.lock();
13 System.out.print(Thread.currentThread().getId() + "\t");
14 lock.unlock();
15 }
16 public void run()
17 { get(); }
18 }
19 public class ReEnterLock {
20 public static void main(String[] args) {
21 LockReEnter demo = new LockReEnter();
22 new Thread(demo).start();
23 new Thread(demo).start();
24 }
25 }
두 번째 줄에서 만든LockReEnter 클래스에 get과 set 방법을 포함하고 get 방법에서 set 방법을 호출했습니다. 단지 get과 set 방법에서 우리는synchronized가 아니라 세 번째 줄에서 정의한 ReentrantLock 형식의 lock 대상으로 다중 스레드의 병발을 관리했습니다. 16줄의run 방법에서 우리는 get 방법을 똑같이 호출했습니다.
main 함수에서, 우리는 똑같이 22줄과 23줄에서 두 차례의 라인을 시작했는데, 이 코드의 운행 결과는 다음과 같다.
8 8 9 9
22줄에서 LockReEnter 형식의 라인을 처음 시작한 후 get 방법을 호출할 때 5줄의 자물쇠 대상을 얻을 수 있습니다. get 방법은 set 방법을 호출합니다. set 방법의 12번째 줄은 다시 자물쇠를 신청하지만 LockReEnter 라인은 get 방법에서 자물쇠를 얻었기 때문에 set 방법에서도 자물쇠를 얻을 수 있습니다. 그래서 처음 실행할 때 get과 set 방법은 같이 실행됩니다.23줄의 두 번째 라인에서 get과 set 방법의 출력을 동시에 출력합니다.
프로젝트의 일부 장면에서 한 라인은 여러 번 잠긴 관련 방법에 들어가야 할 수 있다. 예를 들어 어떤 데이터베이스 작업의 라인은 잠긴 관리의'데이터베이스 연결 얻기'방법을 여러 번 호출해야 한다. 이때 자물쇠를 다시 넣으면 잠긴 문제를 피할 수 있다. 반대로 우리가 자물쇠를 다시 넣을 수 있는 방법이 아니라면 두 번째'데이터베이스 연결 얻기'방법을 호출할 때 잠길 수 있다.이로 인해 자물쇠 문제가 생겼다.
2 공평 자물쇠와 비공평 자물쇠
Semaphore 대상을 만들 때, 우리는 두 번째 파라미터를 통해 이 Semaphore 대상이 공평하게 자물쇠를 잠그는 방식으로 자원을 스케줄링하는지 지정할 수 있습니다.
공평한 자물쇠는 대기 대기열을 유지합니다. 막힌 상태에서 기다리는 여러 개의 라인이 이 대기 대기열에 삽입됩니다. 스케줄링할 때 그들이 요청한 시간 순서에 따라 자물쇠를 가져옵니다. 비공평한 자물쇠에 대해 한 라인이 비공평한 자물쇠를 요청할 때 이 자물쇠가 사용 가능한 상태가 되면 이 라인은 대기 대기열의 모든 대기 라인을 건너뛰고 자물쇠를 얻습니다.
우리가 다시 들어갈 수 있는 자물쇠를 만들 때, 볼 형식의 매개 변수를 가진 구조 함수를 호출하여 이 자물쇠가 공평한 자물쇠인지 아닌지를 지정할 수도 있다.ReentrantLock(boolean fair).
프로젝트에서 자물쇠를 요청하는 평균 시간 간격이 비교적 길면 공평한 자물쇠를 사용하는 것을 권장하고, 반대로 비공평한 자물쇠를 사용하는 것을 권장한다.
예를 들어 서비스 창이 있는데 만약에 비공평하게 잠그는 방식을 사용한다면 창이 비어 있을 때 다음 번호로 오라고 하는 것이 아니라 오기만 하면 서비스한다. 이렇게 하면 창의 비어 있는 대기 시간을 단축하고 단위 시간 내의 서비스 수량(즉 흡수량)을 높일 수 있다.반대로 만약에 이것이 비교적 인기 없는 서비스 창구라면 많은 시간 동안 서비스를 요청하는 빈도가 높지 않다. 예를 들어 한 시간에 한 사람이 왔다면 공평한 자물쇠를 선택할 수 있다.또는 사용자의 평균 대기 시간을 줄이려면 공평한 자물쇠를 선택하면'일찍 도착한 요청이 늦게 처리되는 경우'를 피할 수 있다.
3 읽기 및 쓰기 자물쇠
이전에 우리가synchronized와ReentrantLock을 통해 임계 자원을 관리할 때 한 라인이 잠기면 다른 라인은 이 임계 자원을 조작할 수 없다. 이런 자물쇠는'상호 배척 자물쇠'라고 할 수 있다.
이런 관리 방식에 비해 Reentrant Read Write Lock 대상은 두 개의 자물쇠를 사용하여 임계 자원을 관리한다. 하나는'읽기 자물쇠'이고, 다른 하나는'쓰기 자물쇠'이다.
만약 한 스레드가 자원에 있는'읽기 자물쇠'를 얻게 된다면, 이 자원에 대한'읽기 조작'을 실행하는 다른 스레드는 이 자물쇠를 계속 얻을 수 있습니다. 즉,'읽기 조작'은 동시에 실행할 수 있지만,'쓰기 조작'을 실행하는 스레드는 막힐 것입니다. 만약 한 스레드가 자원에 대한'쓰기 자물쇠'를 얻게 된다면, 다른 자원의'읽기 자물쇠'와'쓰기 자물쇠'를 얻으려는 스레드는 막힐 것입니다.
상호 배열 잠금에 비해 읽기와 쓰기 잠금은 병렬 시 데이터의 정확성을 확보하는 동시에 여러 개의 스레드를 동시에 읽을 수 있도록 하여 효율을 높일 수 있습니다. 아래의 ReadWriteLockDemo.java를 통해 읽기와 쓰기를 통해 읽기와 쓰기를 관리하는 방식을 관찰해 보겠습니다.
1 import java.util.concurrent.locks.Lock;
2 import java.util.concurrent.locks.ReentrantReadWriteLock;
3 class ReadWriteTool {
4 private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
5 private Lock readLock = lock.readLock();
6 private Lock writeLock = lock.writeLock();
7 private int num = 0;
8 public void read() {//
9 int cnt = 0;
10 while (cnt++ < 3) {
11 try {
12 readLock.lock(); System.out.println(Thread.currentThread().getId()
13 + " start to read");
14 Thread.sleep(1000);
15 System.out.println(Thread.currentThread().getId() + " reading," + num);
16 } catch (Exception e)
17 { e.printStackTrace();}
18 finally { readLock.unlock(); }
19 }
20 }
21 public void write() {//
22 int cnt = 0;
23 while (cnt++ < 3) {
24 try {
25 writeLock.lock();
26 System.out.println(Thread.currentThread().getId()
27 + " start to write");
28 Thread.sleep(1000);
29 num = (int) (Math.random() * 10);
30 System.out.println(Thread.currentThread().getId() + " write," + num);
31 } catch (Exception e)
32 { e.printStackTrace();}
33 finally { writeLock.unlock();}
34 }
35 }
36 }
세 번째 줄에 정의된 ReadWriteTool 클래스에서 우리는 네 번째 줄에 읽기와 쓰기 자물쇠를 만들었고, 다섯 번째 줄과 여섯 번째 줄에서 각각 읽기와 쓰기 자물쇠의readLock과writeLock 방법을 통해 읽기와 쓰기 자물쇠를 만들었다.
8줄의read 방법에서 우리는 먼저 12줄의 코드를 통해'읽기 자물쇠'를 추가한 다음에 15줄에서 읽기 조작을 한다. 21줄의write 방법에서 우리는 먼저 25줄의 코드를 통해'쓰기 자물쇠'를 추가한 다음에 30줄에서 쓰기 조작을 한다.
37 class ReadThread extends Thread {
38 private ReadWriteTool readTool;
39 public ReadThread(ReadWriteTool readTool)
40 { this.readTool = readTool; }
41 public void run()
42 { readTool.read();}
43 }
44 class WriteThread extends Thread {
45 private ReadWriteTool writeTool;
46 public WriteThread(ReadWriteTool writeTool)
47 { this.writeTool = writeTool; }
48 public void run()
49 { writeTool.write(); }
50 }
37줄과 44줄에서 우리는 각각 이 두 라인을 읽고 쓰는 것을 정의했다. ReadThread 라인의run 방법에서 우리는 ReadWriteTool 종류의read 방법을 사용했고 WriteThread 라인의run 방법에서는 write 방법을 사용했다.
51 public class ReadWriteLockDemo {
52 public static void main(String[] args) {
53 ReadWriteTool tool = new ReadWriteTool();
54 for (int i = 0; i < 3; i++) {
55 new ReadThread(tool).start();
56 new WriteThread(tool).start();
57 }
58 }
59 }
main 함수의 53줄에서 ReadWriteTool 형식의 도구 대상을 만들었습니다. 55줄과 56줄에서 읽기와 쓰기 라인을 초기화할 때 이 도구 대상을 전송했습니다. 즉, 54줄 for 순환을 통해 만들고 시작하는 여러 개의 읽기와 쓰기 라인은 같은 읽기와 쓰기 자물쇠를 통해 읽기와 쓰기를 제어하고 실행합니다.
다중 스레드가 병렬적으로 스케줄링되는 원인으로 인해 우리는 매번 운행할 때마다 서로 다른 결과를 얻을 수 있지만 이러한 서로 다른 결과에서 우리는 읽기와 쓰기 자물쇠가 읽기와 쓰기를 조율하고 관리하는 방식을 뚜렷하게 알 수 있다. 예를 들어 아래의 일부 출력 결과를 볼 수 있다.
1 8 start to read
2 10 start to read
3 12 start to read
4 8 reading,0
5 10 reading,0
6 12 reading,0
7 9 start to write
8 9 write,2
9 11 start to write
10 11 write,6
여기에서 우리는 ReadWriteTool 클래스의 읽기와 쓰기 자물쇠를 통해 그 중num값을 관리합니다. 1번부터 6번까지의 출력에서 볼 수 있습니다. 8번 라인이 자물쇠를 읽고 num 자원을 읽기 시작했을 때 10번과 12번 라인은 여전히 자물쇠를 읽을 수 있어num 자원을 병렬적으로 읽을 수 있습니다.그러나 읽기 작업 기간에는 쓰기 작업이 있는 라인이 들어갈 수 없습니다. 즉, num 자원에 읽기 자물쇠가 있는 동안 다른 라인은 이 자원에 있는 쓰기 자물쇠를 얻을 수 없습니다.
7번부터 10번까지의 출력에서 알 수 있듯이 9번 라인이num자원의'쓰기 자물쇠'를 얻을 때 다른 라인은 이 자원의'읽기 자물쇠'와'쓰기 자물쇠'를 얻을 수 없다. 11번 라인은 반드시 9번 라인이'쓰기 자물쇠'를 방출한 후에num자원의'쓰기 자물쇠'를 얻을 수 있다.
만약 프로젝트에서 일부 자원(예를 들어 파일)에 대해 읽기와 쓰기 조작이 있다면, 여러분은 읽기와 쓰기 자물쇠를 사용할 수 있습니다. 만약에 읽기 조작의 수량이 쓰기 조작을 훨씬 초과할 경우, 읽기와 쓰기 자물쇠로 읽기와 쓰기를 병행하여 실행할 수 있도록 하여 성능을 향상시킬 수 있습니다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.