본문 바로가기
Java, JSP

[Java/자바] 쓰레드/Thread 사용법 (상속, Runnable)

by LasBe 2022. 1. 19.
반응형

⚡️쓰레드(Thread)란


쓰레드는 간단하게 하나의 프로세스에서 독립적으로 실행되는 하나의 일 또는 작업의 단위를 뜻합니다.

 

자바에서 흔히 사용하는 main() 내 실행문 또한 하나의 쓰레드이며

프로그램 실행 시 처음으로 실행되는 쓰레드이기 때문에 메인 쓰레드라고도 합니다.

 

이러한 쓰레드를 사용 시 장점과 단점은 다음과 같습니다.

 

장점

  • 쓰레드끼리 메모리를 공유함으로써 자원의 절약
  • 동시에 여러 작업이 가능

단점

  • 실행 단위가 많아질수록 코드가 난해해진다.
  • 예상치 못한 충돌로 인한 버그가 발생할 수 있다.

그럼 자바에서의 쓰레드(Thread) 사용법동기화(Synchronized)를 통한 멀티쓰레드 사용법을 알아보겠습니다.

 

 

⚡️Thread 사용법


자바에서 쓰레드를 사용하기 위해서는 다음과 같이 2가지 방법이 존재합니다.

  1. Thread 클래스 상속
  2. Runnable 인터페이스 구현

Thread 클래스나 Runnable 인터페이스를 상속받은 클래스에서

run() 메소드를 오버라이딩해서 실행시킬 코드를 메소드 안에 작성한 후, start() 메소드로 쓰레드를 호출해줍니다.

 

일반적으로 다른 클래스를 상속받기 위해 Thread 클래스를 상속받기보단 Runnable 인터페이스를 구현합니다.

2022.01.01 - [Language/Java] - [Java/자바] interface 인터페이스 사용법

 

그러면 코드와 함께 사용법을 알아보겠습니다.

 


📌 Thread 클래스 상속

1. 클래스에서 Thread 상속하기

class extendsThreadEx extends Thread{
	extendsThreadEx(String threadName){
		super(threadName); // thread 클래스의 슈퍼클래스는 생성자로 이름 지정이 가능하다.
	}
	@Override
	public void run() {
		for(int i=0;i<5;i++) {
			// Thread.currentThread().getName() 쓰레드 이름 리턴
			System.out.println(i+"="+Thread.currentThread().getName());
		}
	}
}

Thread 클래스를 상속할 경우 클래스 내에서 슈퍼클래스의 생성자를 통해 쓰레드의 이름을 정해줄 수 있습니다.

 

run() 메소드 같은 경우 Thread 클래스를 상속받았다면 필수적으로 오버라이드를 해야만 합니다.

 

저 같은 경우 run() 메소드 안에 쓰레드의 작동을 알아보기 위해 반복문으로 1~5의 숫자와 쓰레드 이름을 출력하도록 하였습니다.

 

 

2. 객체화

// main에 throws 처리 아니면 run() 메소드에 try, catch문으로 예외처리
public static void main(String[] args) throws InterruptedException{
	System.out.println("main 스레드 시작");
    
	extendsThreadEx et1 = new extendsThreadEx("First");
	extendsThreadEx et2 = new extendsThreadEx("Second");
	extendsThreadEx et3 = new extendsThreadEx("Third");
	
	et1.start(); et2.start(); et3.start();
}

위에서 구현한 클래스를 객체화시키며 3개 쓰레드의 이름을 First, Second, Third로  정해줍니다.

 

그 후 오버라이드 한 run() 메소드를 start() 메소드로 쓰레드를 실행시킵니다.

 

만약 메인문에 1부터 5까지 출력하는 반복문을 실행시켰다면 1 2 3 4 5가 연속으로 3번 실행되었겠지만

쓰레드의 특징인 동시성 때문에 출력이 뒤죽박죽 섞여 있는 것을 볼 수 있습니다.

 

이를 통해 쓰레드를 이용하면 동시에 여러 일들을 한다는 걸 알 수 있습니다.

 


📌 Runnable 인터페이스 구현

1. 클래스에서 Runnable 인터페이스 구현

class RunnableEx implements Runnable{
	@Override
	public void run() {
		for(int i=0;i<5;i++) {
			// Thread.currentThread().getName() 쓰레드 이름 리턴
			System.out.println(i+"="+Thread.currentThread().getName());
		}
	}
}

Runnable 인터페이스를 통한 상속은 Thread 상속과 대부분 비슷합니다.

 

Thread 상속과 같이 run() 메소드를 필수적으로 오버라이딩 해주어야 합니다.

 

생성자를 통한 이름 지정은 불가능하기 때문에 객체화 과정에서 따로 지정해주어야 합니다.

 

 

2.객체화

public static void main(String[] args) throws InterruptedException{
	System.out.println("main 스레드 시작");
    
	RunnableEx r = new RunnableEx();
	
	Thread t1 = new Thread(r,"First");
	Thread t2 = new Thread(r,"Second");
	Thread t3 = new Thread(r,"Third");
	
	t1.start();t2.start();t3.start();
}

Runnable의 쓰레드 실행 방식은 살짝 다릅니다.

 

Thread 클래스를 상속받은 클래스의 객체는 바로 start() 메소드를 사용 가능하지만,

 

Runnable 인터페이스를 상속받은 클래스는 객체를 생성 후 Thread 객체를 따로 생성해주어서

파라미터로 넣어주어야 start() 메소드를 사용해 쓰레드를 실행 가능합니다.

 

쓰레드가 동시에 실행되기 때문에 결과는 예측할 수 없지만 동시성을 잘 나타내고 있습니다.

 

 

 

⚡️멀티쓰레드와 동기화(Synchronized)


자바에서 지원하는 Synchronized 키워드는 여러 개의 쓰레드가 하나의 자원을 사용하고자 할 때,

현재 데이터를 사용하는 해당 쓰레드를 제외하고 나머지 쓰레드들을 접근하지 못하게 막는 개념입니다.

 

멀티쓰레드 환경에서 서로 공유하고 수정할 수 있는 데이터가 동기화되지 않은 채

이리저리 사용되면 안정성과 신뢰성이 보장되지 않습니다.

 

이에 Synchronized 키워드를 통해 쓰레드 간 동기화를 시켜 데이터의 안정성을 보장합니다.

 

그러나 Synchronized 키워드를 남발하게 되면 오히려 프로그램의 성능 저하를 일으킬 수 있다고 하니 적절한 사용이 권장됩니다.

 

그럼 동기화에 대한 직접적인 예시를 코드로 알아보겠습니다.

 

 

1. Thread 간 동기화되지 않은 코드

class ATM implements Runnable{
	private int money = 100000;
    
	public void run() {
		while (true) {
			if (money <= 0) break; // 돈이 다 떨어지면 종료
			withdraw();
		}}
	void withdraw() {
		if(money<=0) return;
		money -= 10000;
		System.out.println(Thread.currentThread().getName()+" 10000원 출금. 잔액 : "+money);
	}}

public class NonSynchronizedEx {
	public static void main(String[] args) throws InterruptedException{
    
		ATM atm = new ATM();
        
		Thread son = new Thread(atm, "아들");
		Thread daughter = new Thread(atm, "딸");
		Thread mother = new Thread(atm,"엄마");
		Thread father = new Thread(atm,"아빠");
        
		son.start(); daughter.start(); mother.start(); father.start();
	}
}

100,000원이 들어있는 계좌에서 가족 4명이 만원씩 출금한다는 가정하에 작성한 코드입니다.

 

run() 메소드는 오버라이딩하여 만원씩 출금하는 whitdraw() 메소드를 돈이 다 떨어질 때까지 반복해서 돌려줍니다.

 

메인문에서는 Runnable 인터페이스를 구현한 ATM 클래스의 객체를 가족 4명의 쓰레드의 생성자에 넣어주고 실행시킵니다.

 

그럼 결과는 어떻게 나올까요?

쓰레드의 동시성 때문에 잔액이 제대로 계산되지 않는 것을 볼 수 있습니다.

 

원래는 한 줄씩 출력될 때마다 만원씩 빠지는 모습을 보여야 하지만 맨 위 아들은 만원을 출금했다가 4만원이 빠진 모습을 보입니다.

 

실제 은행에서 출금할 때 이러한 모습을 보이면 많이 당황스러울 텐데요, 이를 해결하기 위해 Synchronized 키워드를 추가해줍시다.

 

 

2. Thread간 동기화된 코드

synchronized void whitdraw() { // 동기화
	if(money<=0) return;
	money -= 10000;
	System.out.println(Thread.currentThread().getName()+" 10000원 출금. 잔액 : "+money);
}

간단하게 계좌에서 만원씩 빼가는 코드인 withdraw() 메소드에 Synchronized 키워드를 더해,

한 명이 계좌를 이용 중이면 다른 가족이 계좌를 사용하지 못하게 막아주면 해결됩니다.

 

 

더보기

자바 스레드, 자바 쓰레드, 자바 thread, 자바 동기화, 자바 synchronized, 자바 멀티쓰레드, 자바 runnable 

반응형

댓글


오픈 채팅