본문 바로가기
☕Java/Java 기본

[20210524] Java 스레드(thread)

by 캔 2021. 5. 24.

스레드란 하나의 프로세스(혹은 프로그램) 안에서 실행되는 일련의 작업이다. 자바에서는 스레드를 쉽게 다룰 수 있다. 스레드를 여러 개 사용하는 것을 멀티스레딩(multi-threading)이라고 한다. 멀티스레딩을 위해서 스레드를 생성하는 법을 알아보자.

 

자바에서 스레드를 생성하기 위해서는 두 가지 방법이 있다. 첫 번째는 Thread 추상 클래스를 상속받는 클래스를 만드는 것이며, 두 번째는 Runnable 인터페이스를 구현하는 클래스를 만드는 것이다.

 

먼저, Thread 클래스를 이용한 방법을 알아보자.

//MyClass.java
package Day10;

public class MyClass extends Thread {

	@Override
	public void run() {
		for(int i=0; i<10; i++) {
			System.out.println(Thread.currentThread().getId()+"값"+i);
			try {
				Thread.sleep(100);
			} catch(InterruptedException ie) {
				ie.printStackTrace();
			}
		}
	}
}

MyClass 파일에서 MyClass 클래스를 만들고 Thread 클래스를 상속받는다. 그리고 run 메서드를 오버라이드하여 스레드 아이디와 1~9까지 숫자를 출력하는 작업을 수행하도록 만든다. 그리고 한 번 출력한 뒤에는 1초(=100밀리 초)를 쉬어준다.

//MyClassEx.java
package Day10;

public class MyClassEx {
	public static void main(String[] args) {
		MyClass mc1 = new MyClass();
		mc1.start();
		MyClass mc2 = new MyClass();
		mc2.start();		
	}

}

MyClassEx 파일에서는 MyClass인 mc1과 mc2를 생성하여 각각의 스레드를 start() 메서드를 사용해 시작해준다. 그러면 mc1 객체는 하나의 스레드로 작업을 수행하고 mc2는 다른 하나의 스레드로 작업을 수행한다.

 

이번엔 Runnable 인터페이스를 이용한 방법을 알아보자.

//MyClass.java
package Day10;

public class MyClass implements Runnable {

	@Override
	public void run() {
		for(int i=0; i<10; i++) {
			System.out.println(Thread.currentThread().getId()+"값"+i);
			try {
				Thread.sleep(100);
			} catch(InterruptedException ie) {
				ie.printStackTrace();
			}
		}
	}
}

MyClass 파일에서 MyClass 클래스를 만들고 Runnable 인터페이스를 구현한다. 그리고 run 메서드를 오버라이드하여 스레드 아이디와 1~9까지 숫자를 출력하는 작업을 수행하도록 만든다. 그리고 한 번 출력한 뒤에는 1초(=100밀리 초)를 쉬어준다.

//MyClassEx.java
package Day10;

public class MyClassEx {
	public static void main(String[] args) {
		Thread t1 = new Thread(new MyClass());
		t1.start();
		Thread t2 = new Thread(new MyClass());
		t2.start();
	}
}

MyClassEx 파일에서는 실행 가능한(Runnable)한 클래스인 MyClass를 매개변수로 받는 스레드 t1과 t2를 생성한다. 스레드 t1과 t2는 start() 메서드를 사용해 스레드를 시작한다. 그러면 t1 객체는 하나의 스레드로 작업을 수행하고 t2는 다른 하나의 스레드로 작업을 수행한다.

 

두 방법 모두 추상 클래스와 인터페이스를 상속받기 때문에 run메서드를 오버라이드 해주어야 한다.

 

이렇게 스레드를 사용할 경우 동시에 작업을 처리하다 보면 같은 변수에 접근해야 할 경우가 있다. 만약 한 스레드가 작업을 마치기 전에 다른 스레드가 작업을 시작하여 첫 번째 스레드의 작업을 무시하고 기존 변수 값을 그대로 처리할 경우 변수의 값이 엉망이 될 수가 있다. 이러한 동시 작업 시 변수 접근을 관리하기 위해서는 synchronized라는 키워드를 동시 작업이 필요한 메서드 앞에 붙여주면 된다. ATM의 기능을 구현한 다음의 예제를 살펴보자.

package Day10;
// synchronized: 쓰레드를 사용할 때 같은 데이터를 공유할 때 사용한다.
// balance를 공유하는데, 입출금이 동시에 일어나면 안 된다.
// synchronized 키워드를 사용하면, 하나의 처리가 끝나면 다음 처리가 데이터를
// 사용하게 처리하는 것이다. 
public class ATM {
	String account;
	int balance;
	
	ATM() {
		this.account = "A0001";
		this.balance = 10000;
	}
	
	ATM(String account, int balance) {
		this.account = account;
		this.balance = balance;
	}
	
	//deposit(): 입금하다.
	public synchronized void deposit(int money) {//synchronized 키워드 사용
		balance = balance + money;
		System.out.println(money + "원이 입금되었습니다.");
		System.out.println("잔액은 " + balance + "입니다.");
	}
	
	//withdraw(): 출금하다.
	public synchronized void withDraw(int money) {//synchronized 키워드 사용
		if (balance > money) {
			balance = balance - money;
			System.out.println(money + "원이 출금되었습니다.");
			System.out.println("잔액은 " + balance + "입니다.");
		} else {
			System.out.println("잔액이 모자랍니다.");
		}
	}
}
package Day10;

public class ATMEx {
	public static void main(String[] args) {
		ATM atm = new ATM("홍길동", 10000);
		ATMThread th = new ATMThread(atm);
		th.start();
		
	}
}
package Day10;

import java.util.*;
//Math.random(): 0 ~ 1 사이의 임의의 수

public class ATMThread extends Thread {
	ATM atm;
	Random rnd = new Random();
	
	public ATMThread(ATM atm) {
		this.atm = atm;
	}
	
	public void run() {
		boolean flag = false;
		
		for(int i = 1; i <= 10; i++) {
			int money = rnd.nextInt(10000);// 1~ 10000까지의 임의의 수를 발생시킴
			if(!flag) {
				atm.deposit(money);
			} else {
				atm.withDraw(money);
			}
			flag = !flag;//flag를 반전시킨다.
		}
	}
}