Programming/Java

16. 스레드(Thread)

주죵 2021. 1. 16. 16:38
728x90
반응형

스레드란?

일단 멀티프로세스가 뭔지 알고가자. 멀티프로세스란 컴퓨터가 여러가지 일을 동시에 하고 있는것을 뜻한다.

컴퓨터에서 워드도 하고 파일전송도 하고 채팅프로그램까지 돌리고 있으면 CPU하나에서 여러가지 프로그램이 돌아간다. 이러한것을 멀티프로세스라고 한다.

그렇다면 스레드란 무엇일까? 위에서 말한 프로세스 안에서 다시 여러가지 일을 하는것을 뜻한다. 예를 들어 카톡에서 파일을 전송하면서 채팅도 동시에 할 수 있다.

채팅프로그램 프로세스에서 파일전송 스레드와 채팅 스레드가 같이 돌아가고 있는것이다.

 

Java는 기본적으로 멀티스레드를 지원한다. 스레드는 두가지 형태로 존재하는데 하나는 객체 하나를 n개의 스레드가 공유하는 방식, 또는 하나의 객체당 하나의 스레드가 존재하는 방식이다. 아래의 그림을 참고하자.

출처 : 신입 SW인력을 위한 실전 JAVA

멀티스레드 문법

1) Runnable 인터페이스 구현을 통한 Thread

Runnable이라는 인터페이스를 implements하면 어떤 일을 할때, 로직이 run()메소드 안에 구현하여 스레드를 만든다

public class MainClass {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		ThreadTest threadtest = new ThreadTest();
		Thread thread = new Thread(threadtest,"A"); //threadtest를 이용한 thread형성, 이름은 A
		thread.start(); //start메소드가 run메소드를 호출한다
		
		System.out.println(Thread.currentThread().getName());
		
	}

}

public class ThreadTest implements Runnable {
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName()); //구동되고있는 thread의 이름 반환
		System.out.println("ThreadTest");
		for (int i=0;i<10;i++) {
			System.out.println("i = "+i);
			try {
				Thread.sleep(500); //0.5씩 sleep.
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}

}

출력값은 아래와 같다

main
A
ThreadTest
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9

 

main은 MainClass에서 돌아가는 기본 스레드이름이다. 우리가 스레드를 새로 만들지 않아도 하나의 프로세스에 하나의 스레드가 돌아가기때문에, 그 스레드를 main스레드라고 한다.

그런데 왜 이게 아래에 있어도 먼저 찍힌것일까?  스레드는 별개의 작업이기때문에 메인스레드가 시작되고 우리가 생성한 thread가 run하든 말든 신경을 쓰지 않는다. 따라서 무시하고 내려와 Sysout 값을 출력하고 개별적으로 생성해놓은 thread가 실행되어 이러한 결과를 출력한 것이다. 

 

A는 우리가 ThreadTest 객체로 thread를 만들때 run에서 getName메소드를 호출해놨기떄문에 찍힌 내용이다. 

그리고 0.5초씩 sleep을 걸어놨기때문에 0.5초 간격으로 결과가 출력된다.

 

 

2) Thread 클래스 상속을 통한 Thread

마찬가지로 run 메소드를 오버라이드하여 스레드를 만든다

public class MainClass {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		ThreadTest thread = new ThreadTest(); //thread를 상속받았기 때문에 별도 객체생성 없이 스레드 생성 가능
		thread.setName("B");
		thread.start(); //start메소드가 run메소드를 호출한다
		
		System.out.println(Thread.currentThread().getName());
		
	}

}

public class ThreadTest extends Thread {
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName()); //구동되고있는 thread의 이름 반환
		System.out.println("ThreadTest");
		for (int i=0;i<10;i++) {
			System.out.println("i = "+i);
			try {
				Thread.sleep(500); //0.5씩 sleep.
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}

}

 

 

결과값은 같은 결과에 이름만 B로 바뀌어서 출력된다. 여기서는 Thread를 상속받았기 때문에 별도의 객체생성이 필요없어져서 더 간단하게 구현이 가능하다

 

이번엔 객체 1개에 여러개의 스레드를 구현해보도록 하자. Runnable을 이용한 예시는 아래와 같다.

public class MainClass {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		ThreadTest threadtest = new ThreadTest(); 
		
		Thread thread1 = new Thread(threadtest,"A");
		Thread thread2 = new Thread(threadtest,"B");
		thread1.start();
		thread2.start();
		System.out.println(Thread.currentThread().getName());
		System.out.println("MainClass");
		
	}

}

public class ThreadTest implements Runnable {
	
	int testNum = 0;
	public void run() {
		// TODO Auto-generated method stub
		
		
		for (int i=0;i<10;i++) {
			if(Thread.currentThread().getName().equals("A")) {
				System.out.println("#################################################");
				testNum++;
			}
			System.out.println("ThreadName : "+Thread.currentThread().getName()+", testNum:"+testNum);
			try {
				Thread.sleep(500); //0.5씩 sleep.
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}

}

 

결과값은 아래와 같다.

#################################################
ThreadName : B, testNum:0
ThreadName : A, testNum:1
main
MainClass
#################################################
ThreadName : B, testNum:1
ThreadName : A, testNum:2
#################################################
ThreadName : B, testNum:2
ThreadName : A, testNum:3
#################################################
ThreadName : B, testNum:3
ThreadName : A, testNum:4
#################################################
ThreadName : B, testNum:4
ThreadName : A, testNum:5
#################################################
ThreadName : A, testNum:6
ThreadName : B, testNum:5
#################################################
ThreadName : A, testNum:7
ThreadName : B, testNum:6
#################################################
ThreadName : A, testNum:8
ThreadName : B, testNum:8
ThreadName : B, testNum:8
#################################################
ThreadName : A, testNum:9
ThreadName : B, testNum:9
#################################################
ThreadName : A, testNum:10

 

결과값에서 이름이 A인 thread에서만 testNum이 증가하지만, class안의 변수 testnum은 공유되는걸 확인할 수 있다. 즉 따로 동시에 처리되는 중이란것!

 

객체 하나에 하나의 스레드를 연결시킨 경우를 보자

public class MainClass {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		ThreadTest threadtest1 = new ThreadTest(); 
		ThreadTest threadtest2 = new ThreadTest(); 
		
		Thread thread1 = new Thread(threadtest1,"A");
		Thread thread2 = new Thread(threadtest2,"B");
		thread1.start();
		thread2.start();
		System.out.println(Thread.currentThread().getName());
		System.out.println("MainClass");
		
	}

}

 

서로 다른 객체인 threadtest1과 2에 thread를 하나씩 연결시켰다. 당연히 결과또한 달라진다.

main
MainClass
#################################################
ThreadName : B, testNum:0
ThreadName : A, testNum:1
#################################################
ThreadName : A, testNum:2
ThreadName : B, testNum:0
#################################################
ThreadName : B, testNum:0
ThreadName : A, testNum:3
#################################################
ThreadName : A, testNum:4
ThreadName : B, testNum:0
#################################################
ThreadName : B, testNum:0
ThreadName : A, testNum:5
#################################################
ThreadName : B, testNum:0
ThreadName : A, testNum:6
ThreadName : B, testNum:0
#################################################
ThreadName : A, testNum:7
#################################################
ThreadName : B, testNum:0
ThreadName : A, testNum:8
#################################################
ThreadName : A, testNum:9
ThreadName : B, testNum:0
ThreadName : B, testNum:0
#################################################
ThreadName : A, testNum:10

 

서로 다른 객체이기 떄문에 testNum을 공유하지 않는다. 따라서 if절을 만족시키지 못하는 B이름의 thread는 testNum값이 0으로 유지된다.

 

하나의 객체에 여러개의 스레드를 연결하는 경우 데이터가 꼬여버리는 경우가 발생하게 되므로 신중히 처리해줘야한다.

 

Synchronized

Synchronized의 사전적 의미는 "통합", "동시" 정도 이다. 위에 언급한것처럼 멀티스레드에서 하나의 객체에 n개의 스레드가 진행될 경우 인스턴스 변수 공유로 인해 데이터가 꼬여버리는 문제가 발생할 수있다.

예를들면 식당에 사람이 찾아왔는데, 사람이 이미 있는곳에 다른사람이 와서 합석해버리는건 이상하다. 이런 문제를 Synchronized를 통해 처리할 수 있다.

public class ThreadTest implements Runnable {
	
	 int testNum = 0;
	public synchronized void run() {
		// TODO Auto-generated method stub
		
		
		for (int i=0;i<10;i++) {
			if(Thread.currentThread().getName().equals("A")) {
				System.out.println("#################################################");
				testNum++;
			}
			System.out.println("ThreadName : "+Thread.currentThread().getName()+", testNum:"+testNum);
			try {
				Thread.sleep(500); //0.5씩 sleep.
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}

}

public class MainClass {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		ThreadTest threadtest = new ThreadTest(); 
		
		Thread thread1 = new Thread(threadtest,"A");
		Thread thread2 = new Thread(threadtest,"B");
		thread1.start();
		thread2.start();
		System.out.println(Thread.currentThread().getName());
		System.out.println("MainClass");
		
	}

}

 

위의 예제랑 똑같은 코드이지만 run메소드를 synchronized 처리해줬다. 이러한 경우 동시에 thread가 시작되더라도 먼저들어온 thraed의 처리가 끝나야 다음 thraed가 진행된다. 따라서 결과값은 아래와 같게 나온다

main
MainClass
#################################################
ThreadName : A, testNum:1
#################################################
ThreadName : A, testNum:2
#################################################
ThreadName : A, testNum:3
#################################################
ThreadName : A, testNum:4
#################################################
ThreadName : A, testNum:5
#################################################
ThreadName : A, testNum:6
#################################################
ThreadName : A, testNum:7
#################################################
ThreadName : A, testNum:8
#################################################
ThreadName : A, testNum:9
#################################################
ThreadName : A, testNum:10
ThreadName : B, testNum:10
ThreadName : B, testNum:10
ThreadName : B, testNum:10
ThreadName : B, testNum:10
ThreadName : B, testNum:10
ThreadName : B, testNum:10
ThreadName : B, testNum:10
ThreadName : B, testNum:10
ThreadName : B, testNum:10
ThreadName : B, testNum:10

 

한 스레드가 끝나고 다음 스레드가 진행되어 위와 같은 결과가 나왔다.

 

오늘은 스레드에 대해 알아보았다. 다음 시간엔 자바 그래픽을 알아보도록 하자

 

출처 : 신입 SW 인력을 위한 실전 자바 (by 블스)

728x90

'Programming > Java' 카테고리의 다른 글

18. JAVA 네트워크  (0) 2021.01.17
17. JAVA 그래픽  (0) 2021.01.17
15. JAVA 입출력(I/O)  (0) 2021.01.16
14. JAVA Collections  (0) 2021.01.15
13. 예외처리  (0) 2021.01.13