쓰레드를 여러개 놓고 이들을 동시에 실행하려면 우선 다음과 같은 절차를 밟으면 되는줄 알고 다음과 같은 방법으로 테스트를 해보기로 하였습니다.


1. 쓰레드 몇개 생성

테스트를 위해 더하기, 빼기, 곱하기, 나누기 등을 반복적으로 하는 쓰레드를 다음과 같이 생성해 보았습니다.


(1) Add.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
public class Add extends Thread {
    
    @Override
    public void run() {
        
        for(int i=0; i<100; i++) {
            // Val 클래스의 val이라는 정적 변수에 1 더합니다.
            Val.val++;
            
            // 그리고 변한 값을 콘솔에 보여줍니다.
            System.out.println("Value after addition: " + Val.val);
            
            try {
                // sleep메소드는 일정 시간 동안 메소드의 실행을 일시적으로 중지합니다.
                // 여기서 인자는 1000분의 1초값이므로 0.1초는 100이 됩니다.
                Thread.sleep(100);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
 
cs

(2) Subtract.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
public class Subtract implements Runnable {
 
    @Override
    public void run() {
        
        for(int i=0; i<100; i++) {
            Val.val--;
            System.out.println("Value after subtract: " + Val.val);
            try {
                Thread.sleep(110);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 
}
 
cs

(3) Multiply.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
public class Multiply implements Runnable {
 
    @Override
    public void run() {
        for(int i=0; i<50; i++) {
            Val.val = Val.val * 1.1;
            
            System.out.println("Value after multiplication: " + Val.val);
            try {
                Thread.sleep(200);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
 
cs

(4) Division.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
public class Division implements Runnable {
 
    @Override
    public void run() {
        for(int i=0; i<50; i++) {
            
            Val.val = Val.val / 1.2;
            System.out.println("Value after division: " + Val.val);
            
            try {
                Thread.sleep(220);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 
}
 
cs


2. 공유할 값을 갖는 클래스 생성

1
2
3
4
5
 
public class Val {
    public static double val = 0.0;
}
 
cs


3. 마지막으로 위의 소스코드를 테스트 하기 위해 메인메소드를 갖는 클래스를 만들었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
public class ThreadTest {
    
    public static void main(String args[]) {
        Add add = new Add();
        Subtract subtract = new Subtract();
        Multiply multiply = new Multiply();
        Division division = new Division();
        
        add.start();
        subtract.run();
        multiply.run();
        division.run();
    }
}
 
cs


이렇게 해서 실행을 해봤는데, 기대하기로는 각 쓰레드가 반복하면서 공유하는 값을 각자 가진 연산기능을 수행하여 변경할줄 알았습니다. 그런데 막상 코드를 실행해 보니, 더하기와 빼기 쓰레드 만이 동시에 실행이 되었고, 이들 쓰레드가 종료된 다음에야 곱하기 쓰레드가 실행되고 그다음에 마지막으로 나누기 쓰레드가 실행이 되었습니다. 


기존에 원했던 동시 실행을 하기 위해서 이래저래 조사를 해본 결과, 원하던 바를 구현하기 위해서는 ExecutorService와 Executor라는 클래스를 활용하여야 한다는 것을 알았습니다. 이 두 클래스는 java.util.concurrent 패키지 소속으로, 동시 프로그래밍 (concurrent programming) 구현을 위해 존재하는 라이브러리인 것입니다. 


ExecutorService 클래스는 동시에 실행하고자 하는 쓰레드를 하나의 쓰레드풀로 묶어서 실행할 수 있게 해주며, 따라서 위의 메인메소드를 다음과 같이 수정해 보도록 하겠습니다. 위의 네가지 쓰레드를 하나의 풀로 묶기 위해 ExecutorService의 인스턴스를 생성하고, 거기에 포함시키기 위해 submit() 메소드를 호출하여 각 쓰레드를 담습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class ThreadTest {
    
    public static void main(String args[]) {
        Add add = new Add();
        Subtract subtract = new Subtract();
        Multiply multiply = new Multiply();
        Division division = new Division();
        
        ExecutorService exs = Executors.newFixedThreadPool(4);
        exs.submit(add);
        exs.submit(subtract);
        exs.submit(multiply);
        exs.submit(division);
    }
}
 
cs


submit() 호출 후 별도의 start()나 run() 메소드를 호출하지 않습니다. 이대로 실행해보면 초반에 의도했던대로 쓰레드가 동시에 실행되는 것을 확인할 수 있습니다.

반응형

'Java > 스레드' 카테고리의 다른 글

[자바] Thread (1) - Thread는 무엇인가?  (0) 2017.04.21

우리가 프로그램을 만들어서 그것을 실행할 때, 이것이 실행되는 것 자체를 프로세스라고 합니다. 그리고 프로세스는 최소한 하나의 쓰레드 (Thread)를 갖고 있습니다.


프로세스 자체는 직렬적인 성격을 갖고 있습니다. 즉 프로세스는 순서대로 프로그램을 처리하는데요, 경우에 따라 어떤 두가지 일을 동시에 진행하여야 하는 경우가 있을 수 있습니다. 이 사례는 아래의 코드를 통해 설명하도록 하겠습니다. 아무튼 이렇게 일을 분리하여 동시에 처리하기 위해 도입된 개념을 바로 쓰레드라고 합니다.


자바에서 쓰레드를 구현하는 방법은 크게 두가지가 있습니다.


1. Thread 클래스를 상속하여 구현

2. Runnable 인터페이스를 구현


두가지 쓰레드가 0으로 초기화 된 한 변수를 공유하면서 첫번째 쓰레드는 0.1초마다 그 변수에 1씩 더하는 역할을 총 1000번 하고 두번째 쓰레드는 0.11초마다 1을 빼는 역할을 총 1000회 하는 코드를 만들어 보겠습니다. 따라서 다음과 같이 세가지 클래스를 만듭니다.


(1) 정적 변수 값을 하나 보유한 Val 클래스

1
2
3
4
public class Val {
    public static int val = 0;
}
 
cs

(2) 0.1초마다 Val 클래스의 정적 변수에 1씩 더하는 역할을 총 100번 하는 클래스. 이 클래스는 Thread 클래스를 상속하여 만들어 보겠습니다. 코드에서 보시다시피 실행이 되고자 하는 내용은 run() 메소드를 오버라이드 하여 그 안에 작성을 해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
public class Add extends Thread {
    
    @Override
    public void run() {
 
        // for문 내부의 명령을 100번 실행한다는 의미입니다.
        for(int i=0; i<100; i++) {
            // Val 클래스의 val이라는 정적 변수에 1 더합니다.
            Val.val++;
            
            // 그리고 변한 값을 콘솔에 보여줍니다.
            System.out.println("Value of val: " + Val.val);
            
            try {
                // sleep메소드는 일정 시간 동안 메소드의 실행을 일시적으로 중지합니다.
                // 여기서 인자는 1000분의 1초값이므로 0.1초는 100이 됩니다.
                Thread.sleep(100);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
cs

(3) 0.11초마다 Val 클래스의 정적 변수에서 1씩 빼는 역할을 총 100번 하는 클래스. 쓰레드 구현의 또다른 방법으로 이번에는 Runnable 인터페이스를 구현하여 만들어 보았습니다. 이 경우에도 run() 메소드를 오버라이드 하면서 그 안에 실행되고자 하는 내용을 입력합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
public class Subtract implements Runnable {
 
    @Override
    public void run() {
        
        for(int i=0; i<100; i++) {
            Val.val--;
            System.out.println("Value of val: " + Val.val);
            try {
                Thread.sleep(110);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 
}
cs

(4) 위의 두가지 쓰레드를 실행하는 메인 메소드를 담은 테스트 클래스.

1
2
3
4
5
6
7
8
9
10
11
 
public class ThreadTest {
    
    public static void main(String args[]) {
        Add add = new Add();
        Subtract subtract = new Subtract();
        
        add.start();
        subtract.run();
    }
}
cs


코드에서 보면, 한가지 눈에 들어오는 차이점이 있습니다. Thread 클래스를 상속하여 만든 add 인스턴스를 실행하기 위해서는 start() 메소드를 실행하고, Runnable 인터페이스를 구현하여 만든 subtract 인스턴스를 실행하는 곳에서는 run() 메소드를 실행한다는 것입니다. 왜 그럴까요?


기본적으로 Thread 클래스는 start() 메소드를 호출함으로써 run() 메소드가 실행되도록 되어 있습니다. 비록 위의 add 인스턴스에서 run() 메소드를 직접 호출할 수도 있지만, 그 경우에 결과는 기대했던 것과는 전혀 다르게 나오게 됩니다. 프로그램이 순차적으로 실행되어 add 인스턴스가 갖고 있는 프로그램이 먼저 실행이 되고 이것이 다 끝난 뒤에야 나중에 나오는 subtract 인스턴스가 실행되는 것입니다. 


따라서 앞으로 쓰레드를 활용하여 프로그램을 만들 때, Thread 클래스를 상속한 쓰레드는 run() 메소드를 오버라이드 하여 내용을 채우고 이를 실행할 때는 start() 메소드를 호출한다는 점과 Runnable 인터페이스를 구현한 쓰레드는 run() 메소드 자체를 오버라이드 하여 호출한다는 점을 기억해야 할 것입니다.

반응형

'Java > 스레드' 카테고리의 다른 글

[자바] ExecutorService 클래스 소개  (0) 2017.04.24

+ Recent posts