멀티 스레드 01

오늘·2021년 3월 29일
0

Java

목록 보기
32/42

프로세스와 스레드

프로세스(process)
: 실행중인 하나의 프로그램
: 하나의 프로그램이 다중 프로세스를 만들기도 한다
(ex, 크롬창에서 탭을 두개 이상 만들었을 때)

멀티 태스킹(Multi tasking)
: 두가지 이상의 작업을 동시에 처리하는 것
: 다중 작업

메인(main) 스레드
: 모든 자바 프로그램은 메인 스레드가 main() 메소드를 실행하며 시작
: 메인 스레드는 main() 메소드가 있는 곳
: main()메소드의 첫 코드부터 아래로 순차적으로 실행
-> 실행 종료 조건 : 마지막 코드를 실행했다/ return 문을 만났다

main 스레드는 작업 스레드들을 만들어 병렬로 코드를 실행
: 멀티 스레드를 생성해 멀티 태스팅을 수행하고

프로세스의 종료
: 싱글 - 메인 스레드가 종료하면 프로세스도 종료
: 멀티 - 실행 중인 스레드가 하나라도 있다면 프로세스 미종료됨


-> 멀티 태스킹이 꼭 멀티 프로세스를 뜻하지는 않는다.
-> 멀티 프로세스 : 독립적으로 프로그램들을 실행하고 여러가지 작업 처리
-> 멀티 스레드 : 하나의 프로세스 내에서 여러 스레드가 동시에 작업을 수행하는 것

싱글 스레드를 실행하면 순서에 의해서 한 클래스만 무한반복 되는것에 반하여
멀티 스레드를 실행하면 클래스들이 번갈아가며 출력되는 모습을(멀티로 실행되고 있음을) 확인할 수 있다.


메인 스레드

: 자바 애플리케이션은 메인 스레드(main thread)가 main() 메소드를 실행하면서 시작된다.
: 메인 스레드는 필요에 따라 작업 스레드들을 만들어 병렬로 코드를 실행할 수 있다. 즉, 멀티 스레드를 생성해 멀티 태스킹을 수행하는 것이다.
: 싱글 스레드는 메인 스레드가 종료하면 프로세스도 종료된다. 하지만 실행 중인 스레드가 하나라도 있으면 프로세스는 종료되지 않는다.

아래는 메인 스레드에 있는 코드가 쭉 실행되는 모습이다.

public class ThreadEx01 {
	public static void main(String[] args) {
		System.out.println("hello!");
		Go go = new Go();
		go.go();
		
		
		Come come = new Come();
		come.come();
		}
}

class Go {
	public void go() {
		boolean flag = true;

		while(flag) {
			System.out.println("go");
		}
	}
}

class Come {
	public void come() {
		System.out.println("come!");
	}
}

실행모습

go
go
go
go
go
go
go
go
go
go
go
…

Come은 실행 되지도 않고 go만 계속 출력되는 모습을 확인할 수 있다.


작업 스레드 생성과 실행

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

Thread클래스로부터 직접 생성

java.lang.Thread 클래스로부터 작업 스레드 객체를 직접 생성하려면 Runnable을 매개값으로 갖는 생성자를 호출해야 한다.
Thread thread = new Thread(Runnable target);
Runnable은 인터페이스 타입이기 때문에 구현 객체를 만들어 대입해야 한다.

class Task implements Runnable {
	public void run() {
    		// 실행 코드
        }
}

멀티 스레드(클래스)를 사용하는 순서는 이렇다.

1. 작업 스레드 만들기
 : implements Runnable
2. run() 매소드 재정의 하기
 : 실행코드를 그 안에 작성한다.
3. 메인에서 스레드를 객체화 하면서 작업 스레드를 호출한다.
 : Thread th = new Thread(작업스레드);
4. run()로 호출하는게 아니라 start()로 호출한다.

implements Runnable하고 재정의해 사용하는 모습

public class ThreadEx02 {
	public static void main(String[] args) {
		System.out.println("hello!");
		// 작업 스레드를 객체화 해주고
		Go1 go = new Go1();
		// 스레드를 객체화 하면서 작업 스레드를 넣어준다
		Thread th = new Thread(go);
		// start를 호출하면 재정의 해놨던 run이 호출되어 나온다.
		th.start();
		
		Come1 come = new Come1();
		Thread th2 = new Thread(come);
		th2.start();
		
	}
}

// 작업 스레드 만들기
// Runable 의 것을 재정의 해주어야 한다
class Go1 implements Runnable {
	// 작업할 내용 작성
	@Override
	public void run() {
		boolean flag = true;
		while (flag) {
			System.out.println("go");
		}
	}
}

class Come1 implements Runnable {
	@Override
	public void run() {
		while (true) {
			System.out.println("come!");
		}
	}
}

실행모습

…
come!
come!
come!
come!
come!
come!
come!
come!
go
go
go
go
go
go
go
go
go
go
go
go
…

한 클래스만 출력되는 것이 아니라 번갈아가며 실행되는 모습을 확인할 수 있다.

implements Runnable하고 익명구현 객체로 사용하는 모습

: 클래스를 따로 만들어 재정의 하지 않고, Thread 객체 생성하면서 익명형 구현객체로 바로 생성해 사용할 수도 있다.
: 코드를 좀 더 절약하기 위해 많이 사용되는 방법이다

// 형태는 이렇다.
Thread thread = new Thread( new Runable() {
	public void run() {
    		// 실행코드;
        }
}

-------------------------------------------------
// 사용하는 모습
public class BeepPrintEx {
	public static void main(String[] args) {
		// 클래스를 따로 만들어 재정의 하지 않고
		// Thread 객체 생성하면서 익명형 구현객체로 바로 생성하기
		Thread th = new Thread(new Runnable() {
			@Override
			public void run() {
				Toolkit toolkit = Toolkit.getDefaultToolkit();
				for (int i = 0; i < 5; i++) {
					toolkit.beep();
					try {
						Thread.sleep(2500);
					} catch (Exception e) {
					}
				}
			}
		});
		th.start();

		PrintTask pt = new PrintTask();
		pt.start();
	}
}

class PrintTask extends Thread {
	@Override
	public void run() {
		for(int i=0; i<5; i++) {
			System.out.println("띵");
			try {
				Thread.sleep(500);
			}catch (Exception e) {	}
		}
	}
}

실행모습

띵
띵
띵
띵
띵

화면에 출력이 찍히는 것과 비프음이 나는 것이 함께 진행되는 것을 확인할 수 있었다.


Thread 하위 클래스로부터 생성

작업 스레드가 실행할 작업을 Runnable로 만들지 않고, Thread의 하위 클래스로 작업 스레드를 정의하면서 작업 내용을 포함시킬 수도 있다. 이 경우에는 Thread를 굳이 따로 객체생성할 필요가 없고, run()메소드를 재정의해 사용한다.

public class ThreadEx03 {
	public static void main(String[] args) {
		// Thread를 객체 생성할 필요 없이 그냥 호출해 사용하면 된다
		Go2 go = new Go2();
		go.start();
		
		Come2 come = new Come2();
		come.start();
	}
}

class Go2 extends Thread {
	@Override
	public void run() {
		while(true) {
			System.out.println("go");
		}
	}
}

class Come2 extends Thread {
	@Override
	public void run() {
		while(true) {
		System.out.println("come!");
		}
	}
}

실행모습

…
come!
come!
come!
come!
come!
come!
come!
come!
go
go
go
go
go
go
go
go
go
go
go
go
…

implements와 같이 작업되는 모습을 확인할 수 있었다.


사용해보기 01

// 2단 출력하기 스레드
// A~Z 출력하기 스레드
// 를 멀티로 만들어라

public class Thread_Test {
	public static void main(String[] args) {
		Mul mul = new Mul();
		Thread th = new Thread(mul);
		th.start();
		
		Alpa alpa = new Alpa();
		alpa.start();
	}
}


class Mul implements Runnable{
	@Override
	public void run() {
		for(int i=2; i<3; i++) {
			for (int j=1; j<10; j++) {
				System.out.println(i + "*" + j + "=" + (i*j));
			}
		}
	}
}

class Alpa extends Thread{
	@Override
	public void run() {
		for(int i=65; i<91; i++) {
			System.out.println((char) i);
		}
	}
}

사용해보기 02

// 동시에 다중처리(병렬처리)하는 프로그램 작성

// 구구단(2단 3단)을 출력하는 클래스
// *을 찍는 클래스
// 를 멀티로 작성하여라

public class Thread_Test {
	public static void main(String[] args) {
		Thread th = new Thread(new Runnable() {
			@Override
			public void run() {
				int sum=0;
				for(int i=0; i<100; i++) {
					System.out.println(i);
					sum += i;
				}
				System.out.println("1~100 합 : " + sum);
			}
		});
		th.start();
		
		PrintStar ps = new PrintStar();
		ps.start();
		
		Mul mul = new Mul();
		Thread th2 = new Thread(mul);
		th2.start();
		
		System.out.println("빨리 빨리 일해라");
	}
}

class PrintStar extends Thread{
	@Override
	public void run() {
		for (int i=0; i<10; i++) {
			for(int j=0; j<=i; j++) {
				System.out.print("*");
			}
			System.out.println();
		}
	}
}

class Mul implements Runnable{
	@Override
	public void run() {
		for(int i=2; i<4; i++) {
			for(int j=1; j<10; j++) {
				System.out.println(i + "*" + j + "=" + (i*j));
			}
		}
	}
}

실행모습

…
77
78
빨리 빨리 일해라
**
***
****
*****
******79
80
81
82
83
*******
********
******2*1=2
2*2=4
***
******84
85
86
87
88
…

메인이라고 먼저 실행되는 것도 아니고, 스레드들이 멀티로 처리되고 있는 모습을 확인할 수 있었다.


스레드의 이름

: 우리가 직접 생성한 스레드는 자동적으로 "Thread-n"이라는 이름으로 설정된다 (n은 스레드의 번호를 말한다)
: 큰 역할을 하는 것은 아니지만, 디버깅할 때 어떤 스레드가 어떤 작업을 하는지 조사할 목적으로 가끔 사용된다.

public class ThreadName {
	public static void main(String[] args) {
		Thread mainThread = Thread.currentThread();
		System.out.println("[프로그램 시작 스레드 이름]");
		System.out.println(mainThread);
		
		// 스레드 명을 임의로 작성함
		One one = new One();
		Thread th = new Thread(one);
		th.setName("WorkThread1");
		System.out.println("[작업 스레드 1]");
		System.out.println(th.getName());
		
		// 스레드 명을 임의로 작성하지 않음
		Two two = new Two();
		System.out.println("[작업 스레드 2]");
		two.run();
	}
}

class One implements Runnable {

	@Override
	public void run() {
		System.out.println();
	}
}

class Two extends Thread {
	@Override
	public void run() {
		System.out.println(getName());
	}
}

실행 모습

[프로그램 시작 스레드 이름]
Thread[main,5,main]
[작업 스레드 1]
WorkThread1
[작업 스레드 2]
Thread-1

스레드명을 임의로 지정해주었을 때와 임의로 지정하지 않았을 때, 메인 스레드 명을 확인할 수 있다.


스레드 우선순위

동시성 또는 병렬성으로 실행된다
-> 동시성
: 멀티 작업을 위해 하나의 코어에서 멀티 스레드가 번갈아가며 실행하는 성질
-> 병렬성
: 멀티 작업을 위해 멀티 코어에서 개별 스레드를 동시에 실행하는 성질

스레드는 운영체제가 주도적이기 때문에 사용자가 우선순위를 정해주더라도 상황에 따라 알아서 운영체제가 변동할 수 있다.

스레드 스케줄링에 의해 스레드들은 아주 짧은 시간에 번갈아가면서 그들의 run()메소드를 조금씩 실행한다.

thread.setPriority(우선순위);

스레드 스케줄링
: 우선순위 방식과 순환 할당 방식을 사용

순활 할당 방식(코드로 제어 불가)
: 시간 할당량 정해 하나의 스레드를 정해진 시간만큼 수행

우선순위 방식(코드로 제어가능)
: 우선순위가 높은 스레드가 실행 상태를 더 많이 가지도록 스케줄링
: 1~10 값을 가질 수 있다
: 값을 설정하지 않으면 기본값으로 5가 들어간다.
: 코드의 가독성을 위해 아래와 같이 상수를 사용할 수도 있다.
: 하지만 순위가 더 높다고 무조건 먼저 해결되는 것은 아니다.

thread.setPriority(Thread.MAX_PRIORITY);
thread.setPriority(Thread.NORM_PRIORITY);
thread.setPriority(Thread.MIN_PRIORITY);

다음은 스레드 10개를 생성하고 루핑을 누가 더 빨리 끝낸느가를 테스트한 예제이다. Thread 1~9는 우선순위를 가장 낮게 주었고, Thread10은 우선순위를 가장 높게 주었다.

public class PriorityEx {
	public static void main(String[] args) {
		for(int i=1; i<=10; i++) {
			Thread th = new CalcThread("thread" + i);
			if(i != 10 ) {
				th.setPriority(Thread.MIN_PRIORITY);
			} else {
				th.setPriority(Thread.MAX_PRIORITY);
			}
			th.start();
		}
	}
}


class CalcThread extends Thread {
	public CalcThread(String name) {
		setName(name);
	}
	
	@Override
	public void run() {
		for(int i=0; i<20000000; i++) {
		}
		System.out.println(getName());
	}
}

실행 모습

thread4
thread10
thread2
thread3
thread6
thread1
thread5
thread8
thread9
thread7

우선순위가 가장 높다고 가장 빠르게 처리되는 것은 아니라는 걸 확인할 수 있다.


동기화 메소드와 동기화 블록

스레드에서 반드시 필요하다
-> 자료가 잘못 출력되거나 자료가 변형될 수도 있기 때문에

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

싱글 스레드 프로그램에서는 한 개의 스레드가 객체를 독차지해서 사용하면 되지만, 멀티 스레드 프로그램에서는 스레드들이 객체를 공유해서 작업해야 하는 경우가 있다. 이 경우 의도했던것 과는 다른 결과를 산출할 수도 있다.

스레드가 사용 중인 객체를 다른 스레드가 변경할 수 없도록 하려면 스레드 작업이 끝날 때까지 객체에 잠금을 걸어서 다른 스레드가 사용할 수 없도록 해야한다. 동기화 메소드를 만드는 방법은 다음과 같이 메소드 선언에 synchronized 키워드를 붙이면 된다. 이 키워드는 인스턴스와 정적 메소드 어디든 붙일 수 있다.

public void method() {
	// 여러 스레드가 실행 가능 영역
    	synchronized(공유 객체) {
        	// 임계 영역
            	// 단 하나의 스레드만 실행
        }
        // 여러 스레드가 실행 가능 영역
}

동기화 블록의 외부 코드들은 여러 스레드가 동시에 실행할 수 있지만, 동기화 블록의 내부 코드는 임계 영역이므로 한 번에 한 스레드만 실행할 수 있고 다른 스레드는 실행할 수 없다.

public class MainThreadEx {
	public static void main(String[] args) {
		Calculator calculator = new Calculator();
		
		User1 user1 = new User1();
		user1.setCalculator(calculator);
		user1.start();
		
		User2 user2 = new User2();
		user2.setCalculator(calculator);
		user2.start();
	}
}

class Calculator {
	private int memory;
	public int getMemory() {
		return memory;
	}
	
	public synchronized void setMemory(int memory) {
		// 매개값을 필드에 저장
		this.memory = memory;
		// 스레드를 2초간 일시 정지 시킴
		try {
			Thread.sleep(2000);
		}catch (InterruptedException e) {	}
		// 스레드의 이름
		System.out.println(Thread.currentThread().getName());
		// 스레드 메모리값
		System.out.println(this.memory);
	}
}

class User1 extends Thread {
	private Calculator calculator;
	public void setCalculator(Calculator calculator) {
		this.setName("User1");
		// 공유 객체인 Calculator을 필드에 저장
		this.calculator = calculator;
	}
	@Override
	public void run() {
		calculator.setMemory(100);
	}
}

class User2 extends Thread {
	private Calculator calculator;
	
	public void setCalculator(Calculator calculator) {
		this.setName("User2");
		// 공유 객체인 Calculator을 필드에 저장
		this.calculator = calculator;
	}
	@Override
	public void run() {
		calculator.setMemory(50);
	}
}

실행 모습

User1
100
User2
50

0개의 댓글