본문 바로가기

Web Sever 개발과 CS 기초/자바

Java Runnable Thread 구현과 sysnchronized - 동시성 문제 해결

개요 목적

하나의 프로세스에서 여러가지 일을 처리해주는 Thread를 자바 Runnable 인터페이스로 구현하는 방법에 대해서 알아본다.

Thread에 대한 자세한 이해는 아래 블로그 글에서 확인할 수 있다.

 Thread와 Process Fork 차이

그리고 하나의 공유 자원을 여러 쓰레드에서 동시에 접근하여 생기는 동기화 문제를  방지하는 Synchronized 메소드에 대해서도 알아본다.

Runnable으로 Thread 구현하기

Runnable 인터페이스를 구현하여 Thread 동작을 하는 class를 만들 수 있다.

//Runnable 인터페이스를 상속받아서
//Thread 구현 동작을 run() 메소드 안에 작성한다.
public class RunnableThread implements Runnable{

    @Override
    public void run() {
        System.out.println(LocalTime.now()  + " Thread is started");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(LocalTime.now()  + " Thread is exiting");
    }
}

<실행 하기>

public class Main {

    public static void main(String[] args) throws InterruptedException {
        RunnableThread runnableThread = new RunnableThread();
        //Thread 클래스 생성자에 내가 만든
        //Runnable 구현 클래스를 생성자에 입력하여 Thread를 만든다.
        Thread thread = new Thread(runnableThread);
        

        //system.out.println은 내가 만든 스레드가 아닌
        //메인 스레드이다. 즉 하나의 프로세스에서
        //메인 스레드와 내가 만든 스레드가 동시에 작동되는 것을 확인할 수 있다.
        System.out.println(LocalTime.now() + " Starting the thread");
        //내가만든 Thread 시작
				thread.start();
        System.out.println(LocalTime.now() + " Waiting the thread()");
        thread.join();
        System.out.println(LocalTime.now() + " alive: " + thread.isAlive());
    }
}
14:42:11.288475400 Starting the thread
14:42:11.303132400 Waiting the thread()
14:42:11.303132400 Thread is started
14:42:14.304954400 Thread is exiting
14:42:14.304954400 alive: false

thread.join()을 호출하면 MyThread가 종료될 때까지 main 스레드에서 기다려준다.

thread.join() 메소드가 없었으면, System.out.println(LocalTime.now() + " Thread is exiting"); 가 실행되지 못하고, 메인 스레드가 종료된다.

synchronized 사용하여 동시성 문제 해결

synchronized를 사용하여 하나의 스레드가 조작하고 있는 공유 자원을 다른 스레드가 조작하지 못하도록 Critical Section을 지정할 수 있다.

public class SynchronizedRunnable implements Runnable {

    public int num1 = 0;
    public int num2 = 0;

    @Override
    public void run() {
        for (int i = 0; i < 1000000; i++) {
            num1++;
            //num2의 값에 접근할 때는 
            //하나의 쓰레드만 접근할 수 있다. 
            synchronized (this) {
                num2++;
            }
        }
    }
}

<사용 결과>

public class Main {

    public static void main(String[] args) throws InterruptedException {
        SynchronizedRunnable critical = new SynchronizedRunnable();

        Thread t1 = new Thread(critical);
        Thread t2 = new Thread(critical);
        Thread t3 = new Thread(critical);

        t1.start();
        t2.start();
        t3.start();
        t1.join();
        t2.join();
        t3.join();

        System.out.println(critical.num1);
        System.out.println(critical.num2);
    }
}

num1= 2972276
num2= 3000000

num1은 세 스레드가 동시에 +1을 작업을 하여, 동기화 문제가 발생했다.

num2를 +1할 때는 한번에 하나의 스레드만 접속하여 올바른 계산 값이 나왔다.