[해결] 자바 쓰레드 프로그래밍 질문

GunSmoke의 이미지

예전에 윤경구씨가 마이크로소프트웨어지에 기고하셨던 원고를 보고 있습니다.
자바 쓰레드 관련 부분을 보고 있는데요.
일단 예제는 다음과 같습니다.

import java.io.*;
 
class LockMonitor {
	RandomAccessFile raf=null;
	LockMonitor() {
		try {
			raf=new RandomAccessFile("monitor.txt", "rw");
		} catch (IOException ie) {
			System.err.println("IOException:"+ie.getMessage());
			return;
		}
		Thread thread1=new RaceThread(this, raf);
		thread1.setName("FirstThread");
		thread1.start();
		Thread thread2=new RaceThread(this, raf);
		thread2.setName("SecondThread");
		thread2.start();
		Thread thread3=new RaceThread(raf, raf);
		thread3.setName("ThirdThread");
		thread3.start();
	}
 
	public static void main(String args[]) {
		new LockMonitor();
	}
 
}
 
class RaceThread extends Thread {
	Object monitor;
	RandomAccessFile raf=null;
	RaceThread(Object monitor, RandomAccessFile raf) {
		this.monitor=monitor;
		this.raf=raf;
	}
	// thread class's lifetime
	public void run() {
		try {
			synchronized (monitor) {
				for (int i=0; i<10; i++) {
					raf.writeBytes(
						Thread.currentThread().getName()
						+":["+i+"]"
						+raf.getFilePointer()
						+"\n");
				}
			}
		} catch (IOException ie) {
			ie.printStackTrace();
		}
	}
}

첫째 쓰레드와 둘째 쓰레드는 동일한 this 객체 인스턴스를 모니터 객체로 사용하지만 셋째 쓰레드는 다른 객체를 모니터 객체로 사용하기 때문에 다른 결과를 낸다는 예제입니다. 첫째 쓰레드와 둘째 쓰레드는 상호간에 동기화되지만 셋째 쓰레드는 다른 두 쓰레드의 동기화 여부를 전혀 고려하지 않고 실행된다는 설명입니다. 프로그램 실행 결과 monitor.txt는 이런 식이어야 합니다.

FirstThread:[0]0
FirstThread:[1]17
...
FirstThread:[9]164
SecondThread:[0]183
ThirdThread:[0]383
ThirdThread:[1]402
...
ThirdThread:[5]478
SecondThread:[1]203
...
SecondThread:[4]263
ThirdThread:[6]497
...

하지만 제가 실행한 결과는 (정확히 세어보지는 않았습니다만 꽤 많이 해봤습니다.) 그야말로 똑 같습니다. 첫째, 둘째 셋째 쓰레드가 사이좋게 순서대로 차례대로 실행됩니다.

FirstThread:[0]0
FirstThread:[1]17
FirstThread:[2]35
FirstThread:[3]53
FirstThread:[4]71
FirstThread:[5]89
FirstThread:[6]107
FirstThread:[7]126
FirstThread:[8]145
FirstThread:[9]164
SecondThread:[0]183
SecondThread:[1]203
SecondThread:[2]223
SecondThread:[3]243
SecondThread:[4]263
SecondThread:[5]283
SecondThread:[6]303
SecondThread:[7]323
SecondThread:[8]343
SecondThread:[9]363
ThirdThread:[0]383
ThirdThread:[1]402
ThirdThread:[2]421
ThirdThread:[3]440
ThirdThread:[4]459
ThirdThread:[5]478
ThirdThread:[6]497
ThirdThread:[7]516
ThirdThread:[8]535
ThirdThread:[9]554

이쯤에서 질문 나갑니다.

1. 쓰레드 스케줄러의 불확실성에 대해 모르는 바는 아닙니다만 셋째 쓰레드가 첫째, 혹은 둘째 쓰레드 사이에 실행되지 못하는 이유는 제가 동일한 컴퓨터에서만 실행하기 때문인가요?

2. 이렇게 나오는 것이 정상이라고 할 수 있는 것인가요?

3. 쓰레드는 각각 첫째, 둘째, 셋째 차례대로 생성되지만 실행되는 순서만 스케줄러에 의해 바뀌는 것인가요?

endofhope의 이미지

1. 쓰레드 스케줄러의 불확실성에 대해 모르는 바는 아닙니다만 셋째 쓰레드가 첫째, 혹은 둘째 쓰레드 사이에 실행되지 못하는 이유는 제가 동일한 컴퓨터에서만 실행하기 때문인가요?
-> 아닙니다. cpu 2개이상이 달린 놈으로 동작시키시면 섞인 결과가 나올 수 있습니다. (반드시 섞이지는 않습니다 - 그야말로 스케줄러 마음대로입니다)
cpu가 2개 달린 제 머신 결과를 올려드립니다.

cat monitor.txt 
 
FirstThread:[0]0
FirstThread:[1]17
FirstThread:[2]35
FirstThread:[3]53
FirstThread:[4]71
FirstThread:[5]89
FirstThread:[6]107
FirstThread:[7]126
FirstThread:[8]145
ThirdThread:[1]164  <-- 섞였습니다.
FirstThread:[9]164
SecondThread:[0]202
SecondThread:[1]222
SecondThread:[2]242
SecondThread:[3]262
SecondThread:[4]282
SecondThread:[5]302
SecondThread:[6]322
SecondThread:[7]342
SecondThread:[8]362
SecondThread:[9]382
ThirdThread:[2]202
ThirdThread:[3]421
ThirdThread:[4]440
ThirdThread:[5]459
ThirdThread:[6]478
ThirdThread:[7]497
ThirdThread:[8]516
ThirdThread:[9]535

2. 이렇게 나오는 것이 정상이라고 할 수 있는 것인가요?
-> 네
스케쥴러는 프로그래머가 지정한 차례를 흐트리지 않는 한 (synch 를 깨지 않는 한) 마음대로 스케쥴링 합니다.

3. 쓰레드는 각각 첫째, 둘째, 셋째 차례대로 생성되지만 실행되는 순서만 스케줄러에 의해 바뀌는 것인가요?
-> 네 생성은 차례로 해 주니 ( thread1 = new ... thread2 = new ... thread3 = new ...) 이 차례로 쓰레드는 생성됩니다.

위와 같은 결과는 너무 빨리 일이 끝나서 마치 순서대로 수행되는 것 처럼 보인 것입니다.
처리 시간이 길면 명확히 보이게 할 수 있습니다.

import java.io.*;
 
class LockMonitor {
    RandomAccessFile raf=null;
    LockMonitor() {
        try {
            raf=new RandomAccessFile("monitor.txt", "rw");
        } catch (IOException ie) {
            System.err.println("IOException:"+ie.getMessage());
            return;
        }
        Thread thread1=new RaceThread(this, raf, latencyLock);
        thread1.setName("FirstThread");
        thread1.start();
        Thread thread2=new RaceThread(this, raf, latencyLock);
        thread2.setName("SecondThread");
        thread2.start();
        Thread thread3=new RaceThread(raf, raf, latencyLock);
        thread3.setName("ThirdThread");
        thread3.start();
    }
 
    public static void main(String args[]) {
        new LockMonitor();
    }
    private Object latencyLock = new Object();
}
 
class RaceThread extends Thread {
    Object monitor;
    RandomAccessFile raf=null;
    private Object latencyLock;
    RaceThread(Object monitor, RandomAccessFile raf, Object latencyLock) {
        this.monitor=monitor;
        this.raf=raf;
        this.latencyLock = latencyLock;
    }
    // thread class's lifetime
    public void run() {
        try {
            synchronized (monitor) {
                for (int i=0; i<10; i++) {
                    raf.writeBytes(
                                   Thread.currentThread().getName()
                                   +":["+i+"]"
                                   +raf.getFilePointer()
                                   +"\n");
                    synchronized(latencyLock){
                        try{
                            latencyLock.wait(100);
                        }catch(Exception e){
                            e.printStackTrace();
                        }
                    }
                }
            }
        } catch (IOException ie) {
            ie.printStackTrace();
        }
    }
}

제 3의 Lock 을 사용해서 억지로 처리를 길게 만든 코드입니다.
이 코드를 수행시켜 보시면 쓰레드 1과 3이 핑퐁을 하는 도중이지만 2가 끼여들지 못하고 대기하고 있다가 1과 3이 끝나면 (1과 3이 잡은 latencyLock 과 1이 잡은 file lock 이 풀리면)
그제서야 2가 수행되는 것을 보실 수 있습니다.

결과입니다.

cat monitor.txt 
FirstThread:[0]0
ThirdThread:[0]17
FirstThread:[1]35
ThirdThread:[1]53
FirstThread:[2]71
ThirdThread:[2]89
FirstThread:[3]107
ThirdThread:[3]126
FirstThread:[4]145
ThirdThread:[4]164
FirstThread:[5]183
ThirdThread:[5]202
FirstThread:[6]221
ThirdThread:[6]240
FirstThread:[7]259
ThirdThread:[7]278
FirstThread:[8]297
ThirdThread:[8]316
FirstThread:[9]335
ThirdThread:[9]354
SecondThread:[0]373
SecondThread:[1]393
SecondThread:[2]413
SecondThread:[3]433
SecondThread:[4]453
SecondThread:[5]473
SecondThread:[6]493
SecondThread:[7]513
SecondThread:[8]533
SecondThread:[9]553

--
말할 수 있는 것은 분명하게 말해질 수 있다;
말해질 수 없는 것에 대해서는 침묵해야한다.
논리철학논고 - 루드비히 비트겐슈타인

--
말할 수 있는 것은 분명하게 말해질 수 있다;
말해질 수 없는 것에 대해서는 침묵해야한다.
논리철학논고 - 루드비히 비트겐슈타인

GunSmoke의 이미지

명쾌한 답변, 친절한 해설, 덧붙여 응용 예제까지
너무나 감사드립니다.

大逆戰

大逆戰

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.