안녕! CodeGym에서 멀티스레딩을 공부할 때 "뮤텍스"와 "모니터"의 개념을 자주 접했습니다. 엿보지 않고 어떻게 다른지 말할 수 있습니까? :) 그렇다면 잘하셨습니다! 그렇지 않은 경우(이것이 가장 일반적임) 놀라운 일이 아닙니다. "뮤텍스"와 "모니터"는 실제로 관련된 개념입니다. 또한 다른 웹사이트에서 멀티스레딩에 대한 강의를 읽고 비디오를 볼 때 "세마포어"라는 또 다른 유사한 개념을 보게 될 것입니다. 또한 모니터 및 뮤텍스와 매우 유사한 기능을 가지고 있습니다. 이것이 우리가 이 세 가지 용어를 조사하는 이유입니다. 몇 가지 예를 살펴보고 이러한 개념이 서로 어떻게 다른지 확실하게 이해하게 될 것입니다. :)
뮤텍스
뮤텍스(또는 잠금)는 스레드 동기화를 위한 특수 메커니즘입니다. 하나는 Java의 모든 개체에 "연결"되어 있습니다. 여러분은 이미 알고 있습니다 :) 표준 클래스를 사용하든 자신만의 클래스를 생성하든 상관 없습니다. 예를 들어 Cat 및 Dog : 모든 클래스의 모든 개체에는 뮤텍스가 있습니다 . "뮤텍스"라는 용어는 그 목적을 완벽하게 설명하는 "MUTual EXclusion"에서 유래했습니다. 이전 수업 중 하나에서 말했듯 이 뮤텍스를 사용하면 한 번에 하나의 스레드만 개체에 액세스할 수 있습니다. 뮤텍스의 인기 있는 실제 사례는 화장실과 관련이 있습니다. 사람이 변기 칸막이에 들어가면 안쪽에서 문을 잠급니다. 화장실은 여러 스레드에서 액세스할 수 있는 개체와 같습니다. 파티션 도어의 잠금 장치는 뮤텍스와 같으며 외부 사람들의 줄은 스레드를 나타냅니다. 문에 있는 자물쇠는 화장실의 뮤텍스입니다. 한 사람만 안으로 들어갈 수 있도록 합니다. 즉, 한 번에 하나의 스레드만 공유 리소스로 작업할 수 있습니다. 점유된 리소스에 대한 액세스 권한을 얻으려는 다른 스레드(사람)의 시도는 실패합니다. 뮤텍스에는 몇 가지 중요한 기능이 있습니다. 첫째 , "잠금 해제"와 "잠김"의 두 가지 상태만 가능합니다. 이는 작동 방식을 이해하는 데 도움이 됩니다. 부울 변수(참/거짓) 또는 이진수(0/1)를 사용하여 평행을 그릴 수 있습니다. , 상태를 직접 제어할 수 없습니다. Java에는 명시적으로 개체를 가져오고, 뮤텍스를 가져오고, 원하는 상태를 할당할 수 있는 메커니즘이 없습니다. 즉, 다음과 같은 작업을 수행할 수 없습니다.
Object myObject = new Object();
Mutex mutex = myObject.getMutex();
mutex.free();
즉, 개체의 뮤텍스를 해제할 수 없습니다. Java 머신만이 직접 액세스할 수 있습니다. 프로그래머는 언어 도구를 통해 뮤텍스를 사용합니다.
감시 장치
모니터는 뮤텍스에 대한 추가 "상부 구조"입니다. 실제로 모니터는 프로그래머에게 "보이지 않는" 코드 덩어리입니다. 이전에 뮤텍스에 대해 이야기했을 때 간단한 예를 들었습니다.
public class Main {
private Object obj = new Object();
public void doSomething() {
// ...some logic, available for all threads
synchronized (obj) {
// Logic available to just one thread at a time
}
}
}
synchronized 키워드 로 표시된 코드 블록에서 obj 개체 의 뮤텍스를 획득합니다. 좋아, 우리는 자물쇠를 얻을 수 있지만 정확히 어떻게 "보호"가 제공됩니까? synchronized 라는 단어가 표시되면 다른 스레드가 블록에 진입하는 것을 막는 것은 무엇입니까? 보호는 모니터에서 나옵니다! 컴파일러는 동기화된 키워드를 몇 가지 특수 코드 조각으로 변환합니다. 다시 한 번 doSomething() 메서드가 있는 예제로 돌아가겠습니다 . 우리는 그것에 추가할 것입니다:
public class Main {
private Object obj = new Object();
public void doSomething() {
// ...some logic, available for all threads
// Logic available to just one thread at a time
synchronized (obj) {
/* Do important work that requires that the object
be accessed by only one thread */
obj.someImportantMethod();
}
}
}
다음은 컴파일러가 이 코드를 변환한 후 "내부적으로" 일어나는 일입니다.
public class Main {
private Object obj = new Object();
public void doSomething() throws InterruptedException {
// ...some logic, available for all threads
// Logic available to just one thread at a time:
/* as long as the object's mutex is busy,
all the other threads (except the one that acquired it) are put to sleep */
while (obj.getMutex().isBusy()) {
Thread.sleep(1);
}
// Mark the object's mutex as busy
obj.getMutex().isBusy() = true;
/* Do important work that requires that the object
be accessed by only one thread */
obj.someImportantMethod();
// Free the object's mutex
obj.getMutex().isBusy() = false;
}
}
물론 이것은 실제 사례가 아닙니다. 여기서 우리는 자바 머신 내부에서 일어나는 일을 묘사하기 위해 자바와 유사한 코드를 사용했습니다. 즉, 이 의사 코드는 동기화된 블록 내부의 개체 및 스레드에서 실제로 발생하는 일과 컴파일러가 이 키워드를 프로그래머에게 "보이지 않는" 여러 명령문으로 변환하는 방법에 대한 탁월한 이해를 제공합니다. 기본적으로 Java는 synchronized 키워드를 사용하여 모니터를 나타냅니다 . 마지막 예제에서 synchronized 키워드 대신 나타나는 코드는 모두 모니터입니다.
신호기
멀티스레딩에 대한 개인적인 연구에서 접하게 될 또 다른 단어는 "세마포어"입니다. 이것이 무엇이며 모니터 및 뮤텍스와 어떻게 다른지 알아봅시다. 세마포어는 일부 리소스에 대한 액세스를 동기화하는 도구입니다. 그것의 독특한 특징은 카운터를 사용하여 동기화 메커니즘을 생성한다는 것입니다. 카운터는 공유 리소스에 동시에 액세스할 수 있는 스레드 수를 알려줍니다. Java의 세마포어는 Semaphore 클래스 로 표시됩니다 . 세마포어 개체를 만들 때 다음 생성자를 사용할 수 있습니다.
Semaphore(int permits)
Semaphore(int permits, boolean fair)
다음을 생성자에 전달합니다.
- boolean fair — 스레드가 액세스 권한을 얻는 순서를 설정합니다. fair 가 참 이면요청한 순서대로 대기 중인 스레드에 대한 액세스 권한이 부여됩니다. 거짓이면 스레드 스케줄러에 의해 순서가 결정됩니다.
class Philosopher extends Thread {
private Semaphore sem;
// Did the philosopher eat?
private boolean full = false;
private String name;
Philosopher(Semaphore sem, String name) {
this.sem=sem;
this.name=name;
}
public void run()
{
try
{
// If the philosopher has not eaten
if (!full) {
// Ask the semaphore for permission to run
sem.acquire();
System.out.println(name + " takes a seat at the table");
// The philosopher eats
sleep(300);
full = true;
System.out.println(name + " has eaten! He leaves the table");
sem.release();
// The philosopher leaves, making room for others
sleep(300);
}
}
catch(InterruptedException e) {
System.out.println("Something went wrong!");
}
}
}
프로그램을 실행하는 코드는 다음과 같습니다.
public class Main {
public static void main(String[] args) {
Semaphore sem = new Semaphore(2);
new Philosopher(sem, "Socrates").start();
new Philosopher(sem,"Plato").start();
new Philosopher(sem,"Aristotle").start();
new Philosopher(sem, "Thales").start();
new Philosopher(sem, "Pythagoras").start();
}
}
우리는 조건을 만족시키기 위해 카운터가 2로 설정된 세마포어를 만들었습니다. 두 명의 철학자만 동시에 먹을 수 있습니다. 즉, Philosopher 클래스가 Thread 를 상속하기 때문에 동시에 두 개의 스레드만 실행할 수 있습니다 ! Semaphore 클래스의 capture () 및 release() 메서드는 액세스 카운터를 제어합니다. Acquire() 메서드는 세마포어에 리소스에 대한 액세스를 요청합니다. 카운터가 >0이면 액세스가 허용되고 카운터가 1 감소합니다. release ()메서드는 이전에 부여된 액세스를 "해제"하여 카운터로 반환합니다(세마포어의 액세스 카운터를 1씩 증가). 프로그램을 실행하면 무엇을 얻을 수 있습니까? 문제가 해결되었습니까? 우리 철학자들은 차례를 기다리면서 싸우지 않을까요? :) 우리가 얻은 콘솔 출력은 다음과 같습니다.
Socrates takes a seat at the table
Plato takes a seat at the table
Socrates has eaten! He leaves the table
Plato has eaten! He leaves the table
Aristotle takes a seat at the table
Pythagoras takes a seat at the table
Aristotle has eaten! He leaves the table
Pythagoras has eaten! He leaves the table
Thales takes a seat at the table
Thales has eaten! He leaves the table
우리는 해냈다! 그리고 Thales는 혼자 식사를 해야 했지만 우리가 그를 화나게 했다고 생각하지 않습니다. :) 뮤텍스와 세마포어 사이의 유사점을 눈치챘을 것입니다. 사실, 그들은 동일한 임무를 가지고 있습니다. 일부 리소스에 대한 액세스를 동기화하는 것입니다. 유일한 차이점은 개체의 뮤텍스는 한 번에 하나의 스레드에서만 얻을 수 있지만 스레드 카운터를 사용하는 세마포어의 경우 여러 스레드가 동시에 리소스에 액세스할 수 있다는 것입니다. 이것은 단순한 우연이 아닙니다 :) 뮤텍스는 실제로 세마포어입니다즉, 단일 스레드를 수용할 수 있는 세마포어입니다. 카운터가 1("잠금 해제됨")과 0("잠금됨")의 2개 값만 가질 수 있기 때문에 "바이너리 세마포어"라고도 합니다. 그게 다야! 보시다시피, 결국 그렇게 혼란스럽지 않습니다 :) 이제 인터넷에서 멀티스레딩을 더 자세히 공부하고 싶다면 이러한 개념을 탐색하는 것이 조금 더 쉬울 것입니다. 다음 수업에서 만나요!
GO TO FULL VERSION