[Java] Thread, 동기화

🏃‍♀️·2023년 8월 23일

Java [이론]

목록 보기
4/14

Process

운영체제에서 실행 중인 하나의 어플리케이션
사용자가 애플리케이션을 실행하면 운영체제로부터 필요한 메모리를 할당받아 애플리케이션의 코드를 실행하는것을 의미한다.

Thread

프로세스 내부에서의 코드의 실행 흐름

multi tasking

두 가지 이상의 작업을 동시에 처리하는 것

  • 멀티 프로세스: 워드로 문서 작업하면서 음악을 듣는 것
  • 멀티 스레드: 메신저로 파일 전송을 하면서 채팅을 보내는 것

멀티 프로세스는 운영체제에서 할당받은 자신의 메모리를 가지고 실행되기 때문에 독립적이다.
따라서 하나의 프로세스에서 오류가 발생하더라도 다른 프로세스에 영향을 미치지 않고 메인 스레드가 종료되더라도 실행 중인 멀티스레드가 존재한다면 프로세스가 종료되지 않는다.
그러나 멀티 프로세스는 각각의 별도의 자원을 가지기 때문에 멀티 스레드에 비해 비용이 높습니다.

멀티 스레드는 하나의 프로세스 내부에 생성되기 때문에 하나의 스레드에서 예외가 발생하면 프로세스 자체가 종료될 수 있으며 다른 스레드에 영향을 미칠 수 있고 메인 스레드가 종료되면 프로세스도 종료된다.


Thread 생성하기

Thread 클래스로부터 직접 생성

Thread 클래스로부터 작업 스레드 객체를 생성하려면 Runnable을 매개값으로 가지는 생성자를 호출해야한다.

Thread thread = new Thread(Runnable task);

Runnable은 작업 스레드가 실행할 수 있는 코드를 가지고 있는 인터페이스타입 객체이다. 따라서 구현 객체를 생성하여 run() 메소드를 작성해야한다.

class Task implements Runnable{
	public void run(){
    	// 작업 스레드 구현 내용
    }
}

이 클래스는 스레드가 아니라 작업 내용을 가지고 있는 객체일 뿐이다. 스레드를 구현하려면 이 객체를 매개 값으로 Thread 생성자를 호출해야한다.

Runnable task = new Task();
Thread thread = new Thread(task);

위 방법말고도 익명 객체를 매개 값으로 사용하는 방법도 많이 사용된다. 코드가 간결해진다!
Thread thread = new Thread(new Runnable(){
	public void run(){
    	// 작업 스레드 구현 내용
    }
});

소리와 출력을 동시에 실행하는 멀티 스레드 구현

Task Class 생성

public class Task implements Runnable{
    public void run(){
        for(int i=0; i<5; i++){
            try{Thread.sleep(1000);}catch(Exception e){}
            System.out.println("띵");
        }
    }
}

Runnable 인터페이스를 상속받았으며 "띵"이라는 문자열을 1초 단위로 5번 출력하는 작업을 수행한다.

public static void main(String[] args) {
    Runnable beepTask = new Task();
    Thread thread = new Thread(beepTask);
    thread.start();

    Toolkit toolkit = Toolkit.getDefaultToolkit();
    for(int i=0; i<5; i++){
       toolkit.beep();
       try{Thread.sleep(1000);}catch (Exception e){}
    }
}

Task 클래스의 스레드를 생성하고 start()를 이용하여 스레드를 실행한다.
비프음을 1초 단위로 5번 출력하는 작업을 수행한다.
두 작업은 동시에 진행된다.


익명 구현 객체를 이용

위 코드를 익명 객체를 사용하여 작성하면 아래와 같다.

public static void main(String[] args) {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            for(int i=0; i<5; i++){
                try{Thread.sleep(1000);}catch(Exception e){}
                System.out.println("띵");
            }
        }
    });
    thread.start();

	Toolkit toolkit = Toolkit.getDefaultToolkit();
    for(int i=0; i<5; i++){
        toolkit.beep();
        try{Thread.sleep(1000);}catch (Exception e){}
   }
}

Thread 하위 클래스로부터 생성

public class Task extends Thread{
	@Override
    public void run(){
    	// 작업 스레드 구현 내용
    }
}

Thread 클래스를 상속한 후 run() 메소드를 재정의하여 작업 스레드를 구현한 방법입니다.

익명 자식 객체를 이용하여 구현이 가능합니다.

Thread thread = new Thread(){
	@Override
    public void run(){
    	// 작업 스레드 구현 내용
    }
}

소리와 출력을 동시에 실행하는 멀티 스레드 구현

익명 자식 객체를 이용

public static void main(String[] args) {
    Thread thread = new Thread(){
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                }
                System.out.println("띵");
            }
        }
    };
    thread.start();

	Toolkit toolkit = Toolkit.getDefaultToolkit();
    for(int i=0; i<5; i++){
        toolkit.beep();
        try{Thread.sleep(1000);}catch (Exception e){}
    }
}

동기화

멀티 스레드를 이용하다보면 공유 객체를 사용하는 경우가 생긴다. 이 경우 하나의 스레드가 객체를 이용하고 있는 중이라면 다른 스레드의 접근을 제한하여야한다.

동기화가 되지 않은 유형

계산기(공유 객체)와 사용자 2명이 있다.

계산기 객체 생성

public static class Calculator{
    private int memory;
    public int getMemory(){
        return memory;
    }
    public void setMemory(int memory){
        this.memory = memory;
        try{
            Thread.sleep(2000);
        }catch (Exception e){
            System.out.println("Excepiton");
        }
        System.out.println(Thread.currentThread().getName() + ": " + this.memory);
    }
}

User Thread, main 작성

Calculator calculator = new Calculator();

Thread user1 = new Thread(){
public Calculator calculator1 = calculator;

	@Override
    public void run() {
        calculator1.setMemory(100);
   	}
};
user1.setName("user1");
user1.run();

Thread user2 = new Thread(){
public Calculator calculator2 = calculator;

	@Override
    public void run() {
        calculator2.setMemory(100);
   	}
};      
user2.setName("user2");
user2.run();        

출력 결과 (계속 바뀜)
user1: 100 / user2: 200
user1: 200 / user2: 200
user2: 200 / user1: 200

동기화 구현

프로그램에서 단 하나의 스레드만 실행할 수 있는 영역을 임계영역이라고 한다.
자바에서는 임계영역을 지정하기 위해 동기화 메소드를 제공한다.
스레드가 객체 내부의 동기화 메소드를 실행하면 즉시 객체에 잠금을 걸어 다른 스레드가 동기화 메소드를 실행하지 못하도록 한다.

구현 방법

public synchronized void method(){
	// 임계영역
}

위의 계산기 실습에서는 Calculator 객체의 setMemory 메소드를 선언부를 수정해주면 된다.

public synchronized void setMemory(int memory){...}

결과 출력
User1: 100
User2: 200

0개의 댓글