JAVA/HIGH JAVA

[JAVA] 스레드

아잠만_ 2024. 4. 29. 12:38

프로그램(HDD) -실행-> 프로세스(메모리)

스레드

프로세스 내에서 실제 작업을 수행

모든 프로세스는 하나 이상의 스레드를 가지고있다

프로세스 : 스레드 = 공장 : 일꾼

 

멀티 프로세스 & 멀티스레드

하나의 새로운 프로세스를 생성하는 것 보다

하나의 새로운 스레드를 생성하는 것이 더 적은 비용이듬

 

멀티스레드의 장단점

장점 단점
자원을 보다 효율적으로 사용할 수 있다
사용자에 대한 응답성이 향상된다
작업이 분리되어 코드가 간결해진다

"여러 모로 좋다"
동기화에 주의해야 한다 (자원을 같이씀)
교착상태가 발생하지 않도록 주의해야 한다
각 스레드가 효율적으로 고르게 실행될 수 있게 해야한다

"프로그래밍 할 때 고려해야 할 사항들이 많다."

스레드 구현과 실행

  1. Thread 클래스를 상속
    run()을 오버라이딩(재정의)
  2. Runnable 인터페이스를 구현
    추상메서드 run()을 구현

public class ThreadTest01 {
	public static void main(String[] args) {
		// 싱글 스레드 프로그램
		// 순서대로 처리됨
		for(int i=1; i<=200; i++) {
			System.out.print("*");
		}
		System.out.println();
		System.out.println();
		
		for(int i=1; i<=200;i++) {
			System.out.print("$");
		}
	}
}

Thread를 사용하는 방법

방법1. Thread 상속 (extends)

  1. Thread클래스를 상속한 class를 작성한 후
  2. 이 class의 인스턴스를 생성한다.
  3. 생성된 인스턴스의 start()메서드를 호출해서 실행한다.

public class ThreadTest02 {
	public static void main(String[] args) {
		// 멀티 스레드 프로그램
		// Thread를 사용하는 방법

		// 방법1-2)
		MyThread01 th1 = new MyThread01(); // 객체 생성
		
		// 방법 1-3)
		th1.start();
	}
}

// 방법 1-1 ==> Thread클래스를 상속한 class를 작성한다
class MyThread01 extends Thread{
	// 스레드가 처리할 내용을 run()메서드를 재정의해서 작성한다.
	@Override
	public void run() {
		// 이 run()메서드 안에 이 스레드가 처리할 내용을 작성한다
		for(int i=1; i<=200; i++) {
			System.out.print("*");
		}
	}
}

방법2. Runnable 구현 (implement)

  1. Runnable인터페이스를 구현한 class를 작성한다.
  2. 이 class의 인스턴스를 생성한다.
  3. Thread클래스의 인스턴스를 생성한다. 
    (이 때 2번에서 생성한 인스턴스를 Thread클래스 생성자의 인수값으로 넣어서 생성한다)
  4. 생성된 Thread클래스의 인스턴스의 start()메서드를 호출해서 실행한다.

public class ThreadTest02 {
	public static void main(String[] args) {
		// 멀티 스레드 프로그램
		// Thread를 사용하는 방법

		// 방법 2-2
		MyRunner01 r = new MyRunner01();
		
		// 방법 2-3
		Thread th2 = new Thread(r);
		
		// 방법2-4
		th2.start();
	}
}

// 방법2-1 ==> Runnable인터페이스를 구현한 class를 작성한다.
class MyRunner01 implements Runnable{
	@Override
	public void run() {
		// 이 run()메서드 안에 이 스레드가 처리할 내용을 작성한다
		for(int i=1; i<=200; i++) {
			System.out.print("$");
		}
	}
}

방법3. 방법 2의 다른 방법 :  익명구현체

package kr.or.ddit.basic;

public class ThreadTest02 {
	public static void main(String[] args) {
		Runnable r2 = new Runnable() {	// 객체를 생성함과 동시에 구현을 함
			@Override
			public void run() {
				for(int i=1; i<=200; i++) {
					System.out.print("@");
				}
			}
		};
		
		Thread th3 = new Thread(r2);
		
		th3.start();
	}
}

start() & run()

call stack하나 더 생성 > run이라는 메서드를 새로만든 call stack에 생성한 뒤 start가 사라짐

start()메서드는 스레드가 실행될 환경을 만들고 그곳에서 run이라는 자동으로 호출하게 함

Thread.sleep(시간)

주어진 시간동안 작업을 잠시 멈춘다

시간은 ms단위를 사용한다. 즉, 1000ms은 1초를 의미한다.

class MyThread01 extends Thread{
	@Override
	public void run() {
		for(int i=1; i<=200; i++) {
			System.out.print("*");
			try {
				// Thread.sleep(시간) 메서드 
				Thread.sleep(100);
			} catch (InterruptedException e) {

			}
		}
	}
}

join()

현재의 위치에서 해당 스레드가 종료될 때까지 기다린다.


public class ThreadTest03 {
	public static void main(String[] args) {
		// 스레드가 수행되는 시간 체크해보기
		
		// 1970년 1월 1일 0시 0분 0초 (표준시간)로 부터 경과한 시간을 ms(1/1000초)단위로 반환한다.
		long startTime = System.currentTimeMillis();

		// 처리할 내용...
		Thread th = new Thread(new myRunner02());
		th.start();	// 스레드 작업환경을 만들어주고 run을 호출해주기만함
		
//		start 자체가 실행된 시간만 측정하기 때문에 올바른 방법이 아님
//		join()을 써야 올바른 시간이 측정이 가능하다
		try {
			th.join(); 	// 현재의 위치에서 해당 스레드(현재는 변수 th에 저장된 스레드)가 
					   	// 종료될 때까지 기다린다.
		} catch (InterruptedException e) {
			// TODO: handle exception
		}
		
		long endTime = System.currentTimeMillis();
		
		System.out.println("경과 시간 : "+(endTime-startTime));
		
	}
}

class myRunner02 implements Runnable{
	@Override
	public void run() {
		// 1부터 10억까지의 합계 구하기
		long sum=0L;
		for(long i=1L; i<=1_000_000_000L;i++) {	// _ 숫자의 영향을 주지않음
			sum+=i;
		}
		System.out.println("합계 : "+ sum);
	}
}

단독 스레드 & 멀티 스레드

1 ~ 20억까지의 합계를 구하는 프로그램을 하나의 스레드가 단독으로 처리할 때와
여러개의 스레드가 협력해서 처리할 때 경과 시간을 비교해보기
public class ThreadTest04 {
	public static void main(String[] args) {
		// 단독으로 처리하는 스레드
		SumThread sm = new SumThread(1L, 2_000_000_000L);

		// 여럿이 협력해서 처리하는 스레드들
		SumThread[] smArr = new SumThread[] { new SumThread(1L, 500_000_000L),
				new SumThread(500_000_000L, 1_000_000_000L), new SumThread(1_000_000_000L, 1_500_000_000L),
				new SumThread(1_500_000_000L, 2_000_000_000L), };
		// 단독으로 처리하기...
		long startTime = System.currentTimeMillis();

		sm.start();
		try {
			sm.join();
		} catch (InterruptedException e) {
			// TODO: handle exception
		}
		long endTime = System.currentTimeMillis();
		System.out.println("단독으로 처리할 때의 경과 시간 : " + (endTime - startTime));	
        	// 단독으로 처리할 때의 경과 시간 : 597

		// 여러 스레드가 협력해서 처리하기
		startTime = System.currentTimeMillis();
		for (int i = 0; i < smArr.length; i++) {
			smArr[i].start();
		}
		
		for (SumThread s : smArr) {
			try {
				s.join();
			} catch (InterruptedException e) {
				// TODO: handle exception
			}
		}
		endTime = System.currentTimeMillis();
		System.out.println("협력해서 처리할 때의 경과 시간 : " + (endTime - startTime));	
       	 	// 협력해서 처리할 때의 경과 시간 : 115

	}
}

class SumThread extends Thread {
	private long start;
	private long end;

	public SumThread(long start, long end) {
		this.start = start;
		this.end = end;
	}

	@Override
	public void run() {
		long sum = 0L;
		for (long i = start; i <= end; i++) {
			sum += i;
		}
		System.out.println(start + " 부터 " + end + "까지의 합계 : " + sum);
	}
}

스레드 값 공유하여 다른 스레드에 영향을 미치게하는 방법

10초 안에 입력받는 프로그램 구현
받은 정보가 없을 때는 null
더보기

단독 스레드일 경우에는 각각의 행동으로 받아 프로그램을 구현할 수 없음

import javax.swing.JOptionPane;

public class ThreadTest05 {
	public static void main(String[] args) {
		// 사용자로부터 데이터 입력 받기
//		String str = JOptionPane.showInputDialog("아무거나 입력하세요...");
//		System.out.println("입력한 값 : "+str);

		// 단독스레드와 비슷하기 때문에 시간제한동안 입력받는 작업은 불가능
		// 멀티스레드 이용
		for(int i=10; i>=1; i--) {
			System.out.println(i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO: handle exception
			}
		}
	}
}

멀티 스레드로 static을 이용하는 방법을 사용하기

import javax.swing.JOptionPane;

public class ThreadTest06 {
	public static void main(String[] args) {
		Thread th1 = new DataInput();
		Thread th2 = new MyCountDown();
		
		th1.start();
		th2.start();
	}
}


// 데이터를 입력받는 스레드
class DataInput extends Thread{
	public static boolean inputCheck = false;
	
	@Override
	public void run() {
		String str = JOptionPane.showInputDialog("아무거나 입력하세요...");
		inputCheck = true;	// 입력이 완료되면 inputCheck를 true로 변경
		System.out.println("입력한 값 : "+str);
	}
}

//카운트 다운을 진행하는 스레드
class MyCountDown extends Thread{
	@Override
	public void run() {
		for(int i=10; i>=1; i--) {
			// 입력이 완료되었는 지 여부를 검사해서 입력이 완료되면 스레드 종료시킨다
			if(DataInput.inputCheck) {
				return;
			}
			
			System.out.println(i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO: handle exception
			}
		}
		System.out.println("10초가 지났습니다. 프로그램을 종료합니다..");
		System.exit(0);	// 프로그램 강제 종료
	}
}