멀티 스레드

프로세스내부에서 코드의 실행 흐름을 스레드(thread)라고 한다. 운영체제에서는 실행 중인 하나의 애플리케이션을 

프로세스라고 부른다. 

스레드

운영체제는 두 가지 이상의 작업을 동시에 처리하는 멀티 태스킹을 할 수 있도록 CPU 및 메모리 자원을 프로세스마다

적절히 할당해주고, 병렬로 실행시킨다. 예를 들어 워드로 문서 작업을 하면서 동시에 윈도우 미디어 플레이어로 음악을

들을 수 있다.

 

메인 스레드

자바의 모든 애플리케이션은 메인 스레드가 main() 메소드를 실행하면서 시작한다. 메인 스레드는 main() 메소드의 

첫 코드부터 아래로 순차적으로 실행, main() 메소드의 마지막 코드를 실행하거나 return문을 만나면 실행이 종료

싱글 스레드 애플리케이션에서는 메인 스레드가 종료하면 프로세스도 종료된다. 멀티 스레드 애플리케이션에서는

실행 중인 스레드가 하나라도 있다면, 프로세스는 종료되지 않는다. 메인 스레드가 작업 스레드보다 먼저 종료되더라도

작업 스레드가 계속 실행 중이라면 프로세스는 종료되지 않는다.

 

작업 스레드 생성과 실행

멀티 스레드로 실행하는 애플리케이션을 개발하려면 먼저 몇 개의 작업을 병렬로 실행할지 결정하고 각 작업별로 

스레드를 생성해야 한다.

어떤 자바 애플리케이션이건 메인 스레드는 반드시 존재하기 때문에 메인 작업 이외에 추가적인 병렬 작업의 수만큼

스레드를 생성하면 된다. 자바에서는 작업 스레드도 객체로 생성되기 때문에 클래스가 필요하다.

 

Thread 클래스로부터 직접 생성

java.lang.Thread 클래스로부터 작업 스레드 객체를 직접 생성하려면 밑에 처럼 Runnable을 매개값으로 갖는 생성자를호출해야 한다.

Thread th = new Thread(new Runnable target);

 

Runnable은 작업 스레드가 실행할 수 있는 코드를 가지고 있는 객체라고 해서 붙여진 이름이다.

Runnable은 인터페이스 타입이기 때문에 구현 객체를 만들어 대입해야 한다. Runnable에는 run()메소드 하나가 정의

되어 있는데, 구현 클래스는 run()을 재정즤해서 작업 스레드가 실행할 코드를 작성해야 한다.

package sec01.exam01;

public class Task implements Runnable {

	@Override
	public void run() {
    
		스레드가 실행할 코드;
		
	}
	

}

Runnable은 작업 내용을 가지고 있는 객체이지 실제 스레드는 아니다. Runnable 구현 객체를 생성한 후, 이것을

매개값으로 해서 Thread 생성자를 호출해야 비로소 작업 스레드가 생성된다.

둘 다 동일 

package sec01.exam01;

public class TaskEx {

	public static void main(String[] args) {
//		Runnable task = new Task();
		
//		Thread th = new Thread(new Task());
//		th.start(); // 스레드 실행 요청
	
		// 익명의 구현 객체 ( 해당 방법이 더 많이 사용 됨 ) 
		Thread th = new Thread(new Runnable() {
			
			@Override
			public void run() {
				// 스레드가 실행할 코드
				
			}
		});
			
		
				
	}

}

작업 스레드는 생성되는 즉시 싱행되는 것이 아니라, start( ) 메소드를 다음과 같이 호출해야만 비로소 실행 된다.

thread.start();

start( ) 메소드가 호출되면, 작업 스레드는 매개값으로 받은 Runnable의 run( ) 메소드를 실행하면서 자신의 작업을

처리합니다.

 

인터페이스 정의 

package sec01.exam01;

import java.awt.Toolkit;

// 비프음을 들려주는  작업 정의

public class BeepTask implements Runnable { // 인터페이스

	@Override
	public void run() {
		Toolkit toolkit = Toolkit.getDefaultToolkit();
		for(int i=0; i<5; i++) {
			toolkit.beep();
			try {
				Thread.sleep(500);// 1000 ->1초 
				System.out.println("beeptask");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}

}


상속 정의

package sec01.exam01;

// Thread 하위 클래스로부터 생성

public class BeepThread extends Thread { // 상속

	@Override
	public void run() {
		for(int i=0; i<5; i++) {
			System.out.println("BeepThread");
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}


실행

package sec01.exam01;

public class BeePrintEx {

	public static void main(String[] args) {
		// 스레드 생성, 실행
		Runnable beepTask = new BeepTask();// 비프음을 들려주는  작업 정의 호출
		Thread thread = new BeepThread();
		thread.start();
		
		
		for(int i=0; i<5; i++) {
			System.out.println("띵");
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}

 

 

시간 스레드

정의

package sec01.exam01;

import java.time.LocalDate;
import java.time.LocalTime;

public class Time extends Thread {

	@Override
	public void run() {
		while (true) {
		
		String str = LocalDate.now() + " " + LocalTime.now().toString().substring(0, 8);
		System.out.println(str);	
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
		}
		
	
		
	}
	
	
}


실행

package sec01.exam01;

public class TaskEx {

	public static void main(String[] args) {
//		Runnable task = new Task();
		
//		Thread th = new Thread(new Task());
//		th.start(); // 스레드 실행 요청
	
		// 익명의 객체 생성
		Thread th = new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				
			}
		});
			
		
				
	}

}
<결과>
2023-06-20 17:30:23
2023-06-20 17:30:24
2023-06-20 17:30:25
2023-06-20 17:30:26
2023-06-20 17:30:27
2023-06-20 17:30:28
2023-06-20 17:30:29
2023-06-20 17:30:30
...
현재시간 무한 반복

 

 

 

스레드의 이름

메인 스레드는 'main'이라는 이름을 가지고 있고, 우리가 직접 생성한 스레드는 자동적으로 'Thread-n'이라는 이름으로

설정 된다. n은 스레드의 번호, 다른 이름으로 설정하고 싶다면 Thread 클래스의 setName( ) 메소드로 변경하면 된다.

thread.setName("스레드 이름");

반대로 스레드 이름을 알고 싶을 경우에는 getName( ) 메소드로 변경하면 된다.

thread.getName();

해당 두개는 Thread 클래스의 인스턴스 메소드이므로 스레드 객체의 참조가 필요하다. 만약 스레드 객체의 참조를 가지고

있지 않다면, Thread 클래스의 정적 메소드인 currentThread( )를 이용해서 현재 스레드의 참조를 얻을 수 있다.

Thread th = Thread.currentThread();

 

ThreadA클래스

package sec01.exam02;

public class ThreadA extends Thread {
	
	public ThreadA() {
		setName("ThreadA");
	}
	
	public void run() {
		for(int i=0; i<2; i++) {
			System.out.println(getName() + "가 출력한 내용");
		}
	}
	
}


ThreadB클래스

package sec01.exam02;

public class ThreadB extends Thread {

	@Override
	public void run() {
		for(int i=0; i<2; i++) {
								// 스레드 이름 얻기
			System.out.println(getName() + "가 출력한 내용");// <= ThreadB 실행 내용
		}
	
	}
}



메인 스레드 이름 출력 및 UserThread 생성 및 시작

package sec01.exam02;

public class ThreadNameEx {

	public static void main(String[] args) {
		Thread mainThread = Thread.currentThread(); // 이 코드를 실행하는 스레드 객체 얻기
		System.out.println("프로그램 시작 스레드 이름= " + mainThread.getName());
		
		ThreadA threadA = new ThreadA();
		System.out.println("작업 스레드 이름: " + threadA.getName());
		threadA.start();

		ThreadB threadB = new ThreadB();
		System.out.println("작업 스레드 이름: " + threadB.getName());
		threadB.start();
	}

}
<결과>
프로그램 시작 스레드 이름= main
작업 스레드 이름: ThreadA
ThreadA가 출력한 내용
ThreadA가 출력한 내용
작업 스레드 이름: Thread-1
Thread-1가 출력한 내용
Thread-1가 출력한 내용

 

 

 

동기화 메소드

싱글 스레드 프로그램에서는 1개의 스레드가 객체를 독차지해서 사용하면 되지만, 멀티 스레드 프로그램에서는 스레드

들이 객체를 공유해서 작업해야 하는 경우가 있습니다. 이 경우 주의해야 할 점이 있다.

 

공유 객체를 사용할 때의 주의할 점

멀티 스레드 프로그램에서 스레드들이 객체를 공유해서 작업해야 하는 경우, 스레드 A가 사용하던 객체를 스레드 B가

상태를 변경할 수 있기 때문에 스레드 A가 의도했던 것과는 다른 결과를 산출할 수도 있다.

 

공유 객체

package sec01.exam03;

public class Calculator {
	private int memory;
	
	public int getMemory() {
		return memory;
	}
	
	
//	public void setMemory(int memory) {
//		this.memory = memory;
//		try {
//			Thread.sleep(2000);
//		} catch (InterruptedException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
//		System.out.println(Thread.currentThread().getName() + ":" + this.memory);
//	}
	
	
	// 임계 영역
	public synchronized void setMemory(int memory) {
		this.memory = memory;
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ":" + this.memory);
	}
}


User1 스레드

package sec01.exam03;

public class User1 extends Thread {
	private Calculator calculator;
	
	public void setCalculator(Calculator calculator) {
		this.setName("User1");
		this.calculator = calculator;
	}
	
	public void run() {
		calculator.setMemory(100);
	}
}


User2 스레드

package sec01.exam03;

public class User2 extends Thread {
	private Calculator calculator;
	
	public void setCalculator(Calculator calculator) {
		this.setName("User2");
		this.calculator = calculator;
	}
	
	public void run() {
		calculator.setMemory(50);
	}
}


실행

package sec01.exam03;

public class Calculator {
	private int memory;
	
	public int getMemory() {
		return memory;
	}
	
	
	public void setMemory(int memory) {
		this.memory = memory;
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ":" + this.memory);
	}

}
<결과>
User2:50
User1:50

 

 

 

동기화 메소드

스레드가 사용 중인 객체를 다른 스레드가 변경할 수 없게 하려면 스레드 작업이 끝날 때까지 객체에 잠금을 걸어서

다른 스레드가 사용할 수 없도록 해야한다. 멀티 스레드 프로그램에서 단 하나의 스레드만 실행할 수 있는 코드 영역을

임계 영역이라고 한다. 자바는 임계 영역을 지정하기 위해 동기화 메소드를 제공한다. 스레드가 객체 내부의 동기화 

메소드를 실행하면 즉시 객체에 잠금을 걸어 다른 스레드가 동기화 메소드를 실행하지 못하도록 한다.

 

동기화 메소드를 만들려면 아래 처럼 메소드 선언에 synchronized 키워드를 붙이면 되는데, 인스턴스와 정적 메소드

어디든 붙일 수 있다.

public synchronized void method() {
 임계 영역; // 단 하나의 스레드만 실행
}
위에 코드에서 Calculator만 변경

package sec01.exam03;

public class Calculator {
	private int memory;
	
	public int getMemory() {
		return memory;
	}
	
	 // 임계 영역 ( 스레드 작업이 끝날 때까지 객체에 잠금을 걸음 )
	public synchronized void setMemory(int memory) {
		this.memory = memory;
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ":" + this.memory);
	}
}
<결과>
User1:100
User2:50

 

 

확인 문제 ( 동영상과 음악을 재생하기 위해 두 가지 스레드 실행 ) 

동영상 스레드 정의

package sec01.exam04;

public class MovieThread extends Thread {

	@Override
	public void run() {
		for(int i=0; i<3; i++) {
			System.out.println("동영상을 재생합니다.");
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		}
	}
	
}


음악 스레드 정의

package sec01.exam04;

public class MusicRunnable extends Thread {

	@Override
	public void run() {
		for(int i=0; i<3; i++) {
			System.out.println("음악을 재생합니다.");
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		}
	}
	
}


실행

package sec01.exam04;

public class ThreadEx {

	public static void main(String[] args) {
		Thread thread1 = new MovieThread();
		thread1.start();
		
		Thread thread2 = new Thread(new MusicRunnable());
		thread2.start();

	}

}
<결과>
동영상을 재생합니다.
음악을 재생합니다.
음악을 재생합니다.
동영상을 재생합니다.
음악을 재생합니다.
동영상을 재생합니다.

'프로젝트 기반 자바(JAVA) 응용 SW개발자 취업과정' 카테고리의 다른 글

2023-06-22 25일차  (0) 2023.06.22
2023-06-21 24일차  (0) 2023.06.21
JDBC_설정(MariaDB, 이클립스)  (1) 2023.06.17
2023-06-16 21일차  (0) 2023.06.16
2023-06-15 20일차  (0) 2023.06.15

+ Recent posts