Java고급문법_3_Thread

소정·2023년 2월 5일
0

Java

목록 보기
21/23

스레드(비동기)

작성된 코드를 실행하는 객체, 동시에 여러 작업을 하고싶을 때 쓰는 문법
여러가지 작업 동시에 실행 하고 싶음 => 쓰레드를 다중 생성~!

기본적으로 자바프로그램은 스레드 클래스를 만들지 않아도 기본 스레드 한마리를 만들어 줌 이것을 main Thread 라고 부름

프로세스(현재 실행중인 프로그램)안에서 테스크(하나의 작업 단위)를 쓰레드가 처리함

🧨 Process vs Thread
Process : 실행중인 하나의 자바 프로그램

  • 프로세스들은 자원(글자나 사진 같은 자료같은것)공유가 불가능(프로세스들은 각각 별도의 메모리를 쓰기 때문)

Thread : 하나의 프로세스 안에서 동작하는 직원같은 개념

  • 스레드는 자원공유가 가능 (같은 영역에 있으니까), 스레드는 일회용

프로그램을 읽는 애는 물리적으로 CPU 논리적으론 Thread(한 줄씩 차례대로 읽음)

cf) CPU안에 코어가 진짜 작업자
램 전체를 3개로 나누는 것이 아니고 정해진 영역에서 3개로 나누는것
램이 크든 작든 상관 없고 프로세스마다 받을 수 있는 영역이 다름


스레드 생성 2가지 방법

스레드 혼자 오래 걸리는 작업을 수행하면 다른 코드이 대기상태로 오래동안 실행되지 못할 수 있기에 동시에 여러작업을 하려면 별도의 스레드 객체를 생성하여 요청해야함
스레드를 생성하면 스레드가 동시에 작업 하는게 아니고 번갈아 가면서 하는 것 ( 잡스케줄링) 생성한 스레드가 같은 속도로 실행되진 않음 그때그때 작업스케줄링에 따라 다름

스케줄링
스케줄링은 0~10번으로 나뉘어져 있음
main 스레드는 5번, 0번일 수록 중요도 떨어짐


[1] Thead 클래스 상속

Thread 클래스를 상속(extends)한 클래스를 설계하여 사용하는 방법

Thread 객체를 그냥 new 로 만들어 쓰는 것이 아니라 Thread가 해야하는 작업을 별도로 설계하여 작성해 놓아야함 새 클래스를 만들고 스레드를 extends(상속) 한 뒤 해야할 작업을 반드시 런 메소드(Run()) 안에 쓴다

스레드를 상속받지 않거나 상속 받았더래도 run() 함수 안에 쓰지 않으면 메인 스레드가 읽는 코드가 됨

🔨 사용 방법
class 스레드명 extends Thread{
@Override
public void run() { 실행 코드; }
}
class Main{
ThreadA ta = new ThreadA();
ta.start();
}


public class Main {

	public static void main(String[] args) {
		
		//작업 1. 20개의 파일을 다운로그 하는 작업 - 실제 작업은 불가하니까 느낌만...
		
		for (int i = 0; i < 20; i++) {
			System.out.println(i + "번째 파일 다운로드중");
			
			//강제로 잠시 시간 늘리기
			for(long k=0;k<80000000000000000L; k++) {
				
			}
		}
		
		//작업 2. 20개의 음악을 재생하는 작업
		for (int i = 0; i < 20; i++) {
			System.out.println(i + "번째 음악 재생중");
			
			//강제로 잠시 시간 늘리기
			for(long k=0;k<80000000000000000L; k++) {
				
			}
		}
		
		//별도의 직원(Thread : 코드를 실행하는 녀석)객체 생성하여 각각 위코드(작업)를 실행하도록 요청
		ThreadA ta = new ThreadA();
		//절대 주의)스레드가 해야할 작업을 작성해 놓은 run()메소드는 절대로 직접 호출하지 않는다 ******
		//ta.run(); // --> 금지 : 메인스레드로 가져오는 격******
		ta.start(); // 이렇게 해야 run() 메소드를 지가 처리함 
		
		// ThreadA가 동작하는 동안 mainThread는 다음줄 읽음
		
		ThreadB tb = new ThreadB();
		tb.start();
		
		// ThreadB 동작 시켜놓고 main 은 집 감 하지만 프로세스는 프로그램이 동작하기 떄문에 프로그램이 종료되지는 않음
		
		
		//메인 스레드도 별도 추가 작업
		for (int i = 100; i < 120; i++) {
			//현재 이 코드를 읽고 있는 스레드가 누군지 알려주는 기능
			String name = Thread.currentThread().getName();
			System.out.println(name + " : " + i);
		
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		
		System.out.println("Main method 종료");
	}

}


//1. 파일 다운 수행하는 직원 클래스*(Thread를 상속한)
class ThreadA extends Thread { //상속받지 않으면 main스레드가 읽어주는 애
	//모든 Thread는 이 메소드 안에 작성한 것만 스스로 실행
	
	//스레드 클래스 안에 있는 run()메소드를 오버라이드 해서 사용해야함
	//무조건 run() 안에 써야 함 - 여기가 작업지시서임
	@Override
	public void run() {
		//super.run(); //부모 거 가봐야 암것도 없음 - 삭제
		
		for (int i = 1; i < 21; i++) {
			String name = Thread.currentThread().getName();
			System.out.println(name + " : " + i + "번째 파일 다운로드중");
			
			//잠시 대시.. sleep()
			//밀리세컨드 1/1000초
			try {
				Thread.sleep(500); //0.5초
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
	}
	
//	void aaa() { //런 안에 안쓰면 main Thread가 읽음 소용이 없음
//		
//	}
	
}


//2. 음악 재생 작업을 수행하는 직업 클래스
class ThreadB extends Thread{
	
	@Override
	public void run() {

		for (int i = 1; i < 21; i++) {
			String name = Thread.currentThread().getName();
			System.out.println(name + " : "+ i + "번째 음악 재생중");
			
			//강제로 잠시 시간 늘리기
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	
	}
	
}


[2] Runnable 인터페이스

Runnable인터페이스를 구현(implements)한 클래스를 설계하여 사용하는 방법

Runnable은 인터페이스이기 때문에 객체 생성을 할 수 없다 그래서 얘를 제어해 줄 class가 필요한데 그때 사용하는 클래스가 Thread 클래스!


public class RunnableTest {

	public static void main(String[] args) {

		PersonThread pt = new PersonThread();
		
		//스레드를 실행하려면 run()을 자동실행시켜주는 기능이 작성되어 있는 .start() 메소드를 호출해야함
		//pt.start(); //error - 인터페이스는 추상메소드만 가진애니까 기능이 있을리가 없다

		//Runnable 인터페이스를 구현한 객체의 run()을 실행시켜주는별도의 Thread 객체가 필요함
		//Thread 객체를 생성사면서 생성자메소드의 파라미터로 Runnable을 구현한 객체를 전달하면
		//Thread 객체를 start()할때 자동 run 됨
		
		Thread t = new Thread(pt); //생성자에 Runnable 인터페이스구현한 객체를 보냄
		t.start(); //얜 파라미터로 전달받은 Runnable을 실행헤라는 뜻
		
		System.out.println("aaa");
	
		
		//다중상속이 아닌 그냥 스레드 객체가 필요해도 Runnable로 만들기도 함
		//Runnable r = new Runnable(); //인터페이스는 직접 new 생성 불가
		//인터페이스는 반드시 추상메소드(run)을 구현한 별도의 클래스를 설계한 후
		//객체로 생성해야함
		//실제로 코딩 시 이 변도의 클래스 이름을 명명하는 것도 은근 스트레스
		//이름에 따라서는 이게 스레드인지 잘 인식하지 못하는 이름을 사용할 수도 있음
		MyThread mt = new MyThread();
		
		Runnable rr = new MyThread(); // 업캐스팅
		rr = new PersonThread();
		
		//Thread가 작업할 코드는 별도의 Class 영역에 작성되어 있어서 start()할 때 
		//어떤 동작을 수행할 지 한눈에 들어오지 않음
		//그래서 스레드의 작업코드를 작성하는 run()메소드를 스레드 객체를 생성하면서
		//그자리에서 곧바로 작성하는 문법 생김
		Runnable r; //업캐스팅 사용~! 
		r = new Runnable(){ //객체를 만들면서 클래스를 설계 -> 클래스 이름생략 : 익명 클래스 
			public void run() {
				System.out.println("별도 스레드가 작업할 내용");
			}
		};
		
//		Thread t2 = new Thread(r);
//		t2.start();
		
		new Thread(r).start(); //익명 클래스 : 참조변수 생성 하지 않아도 start() 가능
	
		
		
		
	}

}

class Person {
	String name;
	int age;
	
}

//Person클래스의 멤버를 상속받으면서 Thread의 역할도 하는 클래스를 설계
class PersonThread extends Person implements Runnable{
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		//1초마다 이름,나이를 출력하는 기능코드 작성
		for(int i=0; i<5; i++) {
			System.out.println(name +" : " + age);
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {}
		}
		
	}
	
}


class MyThread implements Runnable{
	@Override
	public void run() {

		
	}
}


☝ Thead 클래스와 Runnable 인터페이스

스레드 생성방법을 두가지로 나눠 놨으나 위 사진에서 볼수 있듯 Thead클래스는 Runnable을 implements 받고 있다 애초에 Thead클래스는 Runnable을 제어하기 위한 클래스(Runnable 트리거)였던 것이다
Runnable을 쓰면 결국 Thread 클래스를 불러야함 그게 귀찮으니까 Thread를 그냥 상속받음 그럼에도 여전히 Runnable 쓰는 이유? 다중상속 효과를 보고 싶어서



동기화 처리

멀티 스레드를 쓰면 무조건 좋냐?
그건 아니다 같은 자원을 동시에 두 놈이 건들 수도 있음(동기화 문제) 이런 문제를 해결해야함 이런 것을 처리하는 것이 동기화(synchronized) 처리 (<-> 비동기 Asynchronize : 스레드처리)

🧨 동기 vs 비동기
동기(Synchronize) : a작업이 끝나면 b동작을 해라
비동기(Asynchronize) : a작업 할 때 b작업을 동시에 하는 것(시작점이 같은 건 아님)

동기화 처리는 빈도의 문제가 아니고 치명적인 오류가 될수도 있다 이러한 것을 막아주기 위한 synchronized

동기화는 안정성을 확보할 수 있지만 남발하면 성능 저하가 일어날 수 있다 때문에 너무 많이 쓰면 스레드를 쓰는 이유가 없다


public class SynchronizedTest {

	public static void main(String[] args) {
		
		//계좌 객체 생성
		Account acc = new Account();

		//이 계좌에 돈을 입금하는 작업을 수행하는 별도 Thread객체 생성
		TestThread t1 = new TestThread(acc);
		TestThread t2 = new TestThread(acc);
		
		t1.start(); //동시 실행
		t2.start();
	
	
	}

}

//입금기능
class Account{
	
	int money = 0;
	
	//1. 동기화 처리 안한 입금 기능
	//별도의 동기화 처리를 하지 않으면 서로다른 스레드가 동시에 이 기능을
	//실행하여 문제가 생길 수 있다
//	void add(int m) {
//		String name = Thread.currentThread().getName();
//		
//		System.out.println(name + " : 입금 작업을 시작합니다");
//		System.out.println(name + " : 현재 잔액 : "+money);
//		money += m;
//		
//		//실제 전산처리를 가장하여 오래걸리는 코드 작성
//		for (long i = 0; i < 10000000000L; i++) {
//			new String("aaaa");
//		}
//		
//		System.out.println(name + " : 입금 후 잔액 : " + money);
//		
//	}
	
	//명령은 겹쳐서 내렸지만 실행하려는 걸 lock을 걸어서 기다리게 만들기 = 동기화
	
	// 1. 동기화 처리한 입금기능 - 메소드가에 동기화 처리 통으로 하면 1번이 끝날 때 까지 2번이 한없이 기다림
//	synchronized void add(int m) {
//		String name = Thread.currentThread().getName();
//		
//		System.out.println(name + " : 입금 작업을 시작합니다");
//		System.out.println(name + " : 현재 잔액 : "+money);
//		money += m;
//		
//		//실제 전산처리를 가장하여 오래걸리는 코드 작성
//		for (long i = 0; i < 10000000000L; i++) {
//			new String("aaaa");
//		}
//		
//		System.out.println(name + " : 입금 후 잔액 : " + money);
//		
//	}
	
	// 2. 동기화 블럭 사용해보기 - 원하는 만큼만 동기화 처리
	void add(int m) {
		String name = Thread.currentThread().getName();
		System.out.println(name + " : 입금 작업을 시작합니다\n");
		
		synchronized (this) { //()안은 내가 어떤 자원을 건드릴때 동기화 할거니? -> 어떤 객체를? -> 현재  Account 
			//내 안에서 나 부르는 키워드 this
			
			System.out.println(name + " : 현재 잔액 : "+money);
			money += m;
			
			//실제 전산처리를 가장하여 오래걸리는 코드 작성
			for (long i = 0; i < 10000000000L; i++) {
				new String("aaaa");
			}
			
			System.out.println(name + " : 입금 후 잔액 : " + money);

		}
		
		
	}
	
}


class TestThread extends Thread{
	
	Account acc; //참조변수
	
	public TestThread(Account acc) {
		this.acc = acc;
	}
	
	@Override
	public void run() {
		
		this.acc.add(100); //100원 입금하는 동작 수행
		
	}
	
}

스레드에서 자주 쓰는 메서드

1)sleap() 와 interrupt()
sleap()은 시간이 정해져 있는 wait 일정 시간동안 자고 알아서 일어남
interrupt() 강제로 깨우기 위해 예외(catch안)를 발생시키는 놈


public class SleapTest {
	
	public static void main(String[] args) {
		
		SThread t = new SThread();
		t.start();
		
		//5초후 깨우기
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {}
		
		//sleep()으로 자고 있는 스레드를 강제로 꺠우기 위해 예외를 발생시키는 문법
		t.interrupt();
		
		
	}
	
}

class SThread extends Thread {
	
	@Override
	public void run() {
		
		//10초 정도 대기 후에 "Hello" 글씨 출력
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) { // 자다가 깨우면 예외처리가 필요해서 예외처리
			System.out.println("자는데 왜 깨워!!");
		}

		System.out.println("Hello");
		
	}
	
}

2)join()와 interrupt()
join() 위 작업이 끝난 후 아래 작업이 하고 싶을 때 위 스레드가 실행하는 사이에 main 스레드 코드가 그냥 실행되어버리는 상황이 싫을 때 사용하는 문법

Synchronize 동기화 문법으론 처리할 수 없을때 (같은 자원을 사용하는 것이 아닐때)
현재 스레드의 작업이 끝날때까지 어떤 스레드라도 무조건 대기시키는 기능


public class JoinTest {

	public static void main(String[] args) {

		
		JThread jt = new JThread();
		jt.start();
		
		//위 작업이 끝난 후 아래 작업이 하고 싶을 때
		//위 쓰레드가 실행하는 사이에 main 스레드 코드가 그냥 실행되어버리는 상황이 싫을 때 사용하는 문법
		//Synchronize 동기화 문법으론 처리할 수 없을때 (같은 자원을 사용하는 것이 아닐때)
		//어던 스레드의 작업이 끝날때까지 어떤 스레드라도 무조건 대기시키는 기능
		
		try {
			jt.join();
		} catch (InterruptedException e1) {
			//조인하는 걸 방해 시킬때 나오는 블럭
		}
		
		
		//메인 스레드가 다른작업을 수행 
		for (int i = 0; i < 10; i++) {
			System.out.println("main");
			
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		
		
		
	}

}
//	<참고>
//	안드로이드는 main은 네트워크 못 건드리고
//	스레드는 화면을 못 건드린다


class JThread extends Thread {
	
	@Override
	public void run() {
		
		for (int i = 0; i < 10; i++) {
			System.out.println("aaa");
			
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		}
		
	}
	
}

3) 양보하는 것

4) stop();
무한 스레드 중 종료시키는 메서드

5) setPriority() : 우선순위 메소드
우선순위가 좋다는 것이 무조건 되는것이 아니고 그냥 확률이 높아지는것뿐임
setPriority()안에 잡스케줄링 번호 0~10 입력
10번일 수록 중요도 높아짐

public class RaceHorse {

	public static void main(String[] args) {
		
		Horse h1 = new Horse("천리안");
		Horse h2 = new Horse("작토마");
		Horse h3 = new Horse("케논");

		System.out.println("경주시작"); 
		
		h1.start();
		h2.start();
		h3.start();
		
		//실행 속도는 동일하지 않음~~
		
		//우선순위가 있다
		h1.setPriority(10); //우선순위 제일 좋음 - 단 sleep이 없어야 효용성 있다
		h2.setPriority(5);  //우선순위가 좋다는 것이 무조건 되는것이 아니고 그냥 확률이 높아지는것뿐임
		h3.setPriority(2);	
		
		//안드로이드에선 필수 main에선 아예 스레드 실행못함
		//오래 걸리는 것 스레드 안해놓으면 프로그램 다운됨
		
	}

}

class Horse extends Thread{
	
	String name;
	
	public Horse(String name) {
		this.name = name;
	}
	
	@Override
	public void run() {
		
		for (int i = 0; i < 20; i++) {
			System.err.println(name + " 뛴다 다그닥 다그닥");
		
			try {
				Thread.sleep(400);
			} catch (InterruptedException e) {}
		}
		
		System.out.println(name + " 도착");
	
	}
	
}

6)wait() / notify() : 반드시 동기화 처리해야됨
애네 둘은 Object거 임
wait()은 시간이 정해져 있지않는 기다림 언제까지? 내가 깨울 때 까지~
notify() 깨우는 명령어


public class ControllThread {

	public static void main(String[] args) {

		// 타이어를 조립 직원 객체 생성
		CThread c = new CThread();
		c.start();
		
		//스레드는 run()가 종료되면 더이상 실행 불가
		//일회용임
		// error : IllegalThreadStateException : run메소듣 한번 하면 끝 일회용임
//		while (true) { 
//			c.start();
//			
//		}
		
//		c = new CThread(); //또 쓰고 싶음 객체 다시 만들어야됨 -> 메모리 남용
//		c.start();
		
		////////////////////////////////////////////////////////////////////////////////
		
		//스레드에게 3초후 잠시 휴식을 주자
		//시간제한 없이 스레드의 동작을 멈추는 기능
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
//		try {
//			c.wait(); // java.lang.IllegalMonitorStateException -> error
//			// wait()은 외부에서 스레드를 강제로 일시정지 하는 것을 권장하지 않음
//		} catch (InterruptedException e1) {
//			// TODO Auto-generated catch block
//			e1.printStackTrace();
//		}
		
		//변경하는 메소드를 정의하여 사용하는 방식 권장!
		c.pauseThread();
		
		////////////////////////////////////////////////////////////////////////////////
		
		//3초후 작업 복귀
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		c.reasumeThread();
		
		
		////////////////////////////////////////////////////////////////////////////////
		
		
		//3초후에 작업 종료
		//스레드를 종료하기
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//c.stop(); //deprecate 가급적 회피하라 - 사용 권장하지 않음
		// => 작업의 동기 문제가 있을 수 있어서 
		// 자원에 대한 리소스가 제대로 마무리 안되는 상황이 생김 - 한던거 마무리 안하고 가버림
		
		
		//while문을 종료하면 결국 run메소드가 종료되므로
		//정상적으로 멈출 수 있음
		//while문의 조건을 거짓으로 변경
		//c.isRun = false; // 가장 안전한 스레드 종료 방법 ->  근데 네거 네가
		
		//객체지향의 특징에 맞게 isRun을 false로 
		//변경하는 메소드를 정의하여 사용하는 방식 권장!
		c.stopThread(); //
		

	}

}

//타이어 조립 스레드
class CThread extends Thread {
	
	boolean isRun = true;
	boolean isWait = false;	

	@Override
	public void run() {
		// 4개 타이어 조립
//		System.out.println("1번 타이어 조립");
//		System.out.println("2번 타이어 조립");
//		System.out.println("3번 타이어 조립");
//		System.out.println("4번 타이어 조립");
//		System.out.println();
//		
//		System.out.println("퇴근");
//		System.out.println();
		
		//2. 여러번 시키고 싶은데 run이 끝나면 퇴근하니까 run안에서 안끝냄
//		while (isRun) { 
//			System.out.println("1번 타이어 조립");
//			System.out.println("2번 타이어 조립");
//			System.out.println("3번 타이어 조립");
//			System.out.println("4번 타이어 조립");
//			System.out.println();
//			
//			try {
//				Thread.sleep(1000);
//			} catch (InterruptedException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
//		}
		
		//3. wait 추가
		while (isRun) { 
			System.out.println("1번 타이어 조립");
			System.out.println("2번 타이어 조립");
			System.out.println("3번 타이어 조립");
			System.out.println("4번 타이어 조립");
			System.out.println();
			
			if(isWait) { //if 조건을 걸어서 true되면 멈춰
				try {
					synchronized (this) { // 이렇게 해도 오류남 -> 동기화문제, this = 이 스레드를 건드리면 이란 뜻
						wait(); //waitPool로 가서 쉼
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				} 
			}
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		System.out.println("퇴근");
		System.out.println();
		
	}
	
	//스레드의 while문 조건을 거짓으로 변경하는 기능 
	//스레드 종료 목적 메소드
	//반드시!! 동기화 처리
	void stopThread() {
		isRun = false;
		
		synchronized (this) { //stop안에 꼭 써줘야함 휴게실에서 자느라 퇴근 못하는 놈 있을까봐
			this.notify(); 
		}
	}
	
	//스레드 종료 목적 메소드
	void pauseThread() {
		isWait = true;
	}
	
	//wait()되어 있는 스레드 복귀 메소드
	//반드시!! 동기화 처리
	void reasumeThread() {
		//notify(); //이렇게 쓰면 문제 있음 얘를 부르는 애를 깨움 이 코드에선 mainThread
		
		isWait = false; //이거 안하면 한번 깨우고 나서 다시 while반복문 때문에 또 멈춰버림
		
		synchronized (this) { 
			this.notify(); 
		}
	}
	

}

class CThread2 extends Thread {
	
	
}



주의!!!!!!!!!!!
네트워크 스레드에 데이터 가져오라고 명령해 놓고
메인스레드에서 바로 보여달라하면 보여줄게 없으니 오류나는 실수 조심


처음 스레드를 접했을 때는 뭔소린지 머리에 하나도 안들어 오고 프로세스는 공장 스레드는 일꾼이라는 것만 기억났는데 다시 보니 이해가 좀 간다
이해가 갔다 해도 이론을 아는것도 사용을 하는 것은 다른 문제라 잘 적용할지는 모르지만,,,우선 이해 했다는 것에 만족,,,,

profile
보조기억장치

0개의 댓글