java

18일차 2023-03-31

choi Hoon 2023. 3. 31. 20:31

1. 스레드

1-1) 프로그램과 프로세스의 차이

ex) 윈도우 키 눌러서 보이는 계산기는 프로그램, 하지만 이걸 실행시키면 메모리 올라감 그럼 이걸 프로세스라 부름.

 

1-2) 스레드란?

 

정의: 하나의 프로세스 안에서 실행되는 명령 흐름.

즉, 프로세스 안의 작은 프로그램이다.

자바는 멀티 스레딩을 지원, 이 지원을 위해 Thread클래스와 Runnable인터페이스 제공함.

 

1-3) 멀티 스레딩이란?

정의: 하나의 프로세스 안에서 여러 개의 스레드가 동시 작업하는 것.

자바에서 main메소드 이것도 하나의 main스레드라고 한다.

근데 단일 스레드임.

Thread클래스를 상속, runnable인터페이스를 상속=> 그래서 다른 스레드를 동작시킬 수 있음.

 

1-4) 스레드의 상태전이

스레드의 상태 전이

start() 호출하면 Runnable => Running => Dead

Running에서 wait()나 Sleep() 호출되면 wait상태 3000(3초) 있다가=>다시 Runnable상태로 감 근데 notify()에서 어떤 걸 호출할지 모름 (pc os마음) (이건 wait()) 거의 안 씀

그래서 sleep() 씀 이것은 그런 거 없이 바로 Runable상태로 바로 감.

 

1-5) 만드는 방법:

1. java.lang.Thread 클래스를 상속받아 구현

2. java.lang.Runnable 인터페이스를 상속받아 구현

스레드를 만드는 방법

 

1-6) Thread의 주요 메소드       

 

-start(): 스레드 시작, 스레드를 Runnable상태로 전이시킴, 스레드의 run()메소드 호출 

.run(): 스케쥴러에 의해 Running상태가 되면 안의 코드 실행. (대기상태는 Runnable)에 위치)

run()메소드 오버라이딩: 개발자가 호출하는 것 아님. Running상태에 들어가면 자동으로 호출되는 메소드이다(이걸 콜백 메소드라 한다)

static int activeCount(): 현재 활성화된 스레드의 수를 리턴 (int)형

static Thread currentThread(): 현재 실행중인 스레드를 리턴()

setName(String name): 스레드의 이름을 주어진 이름으로 변경

String getName(): 스레드의 이름을 리턴

sleep(long milis): 1000분의 1초동안 늦게 실행 ex) sleep(3000) 하면 3초, sleep(7000) 하면 7초대기

static yield(): 현재 실행중인 스레드가 점유한 cpu를 내좋도록 한다.=> 자기와 우선순위가 같거나 높은 스레드에게 실행할 기회를 주는 것을 의미

setPriority(int priorty): 스레드의 우선순위 설정: MAX_PRIORITY, MIN_PRIORIT, NORM_PRIORIT

MAX_PRIORITY: 스레드가 가지는 최대 우선권 value: 10

MIN_PRIORITY: 스레드가 가지는 최소 우선권 value: 1

 NORM_PRIORIT: 스레드가 가지는 디폴트 우선권 value: 5

join(): start() 호출뒤에 join메소드를 호출해야 되며, join()를 호출한 스레드가 다 실행된 상태(Dead상태)가 돼야 다른 스레드가 동작할 수 있다.

하지만,  콘솔창을 보면 맥스가 먼저 출력되지 않기도 한다. 

이 의미는 우선권이지만 먼저 실행된다는 것은 아니라는 것을 의미

다른 스레드도 value값을 10, 내 스레드도 value값을 10을 의미하면 어떻게 할 것인가 

결국 스케쥴러에 의해 정해져서 의미가 없다.

isAlive(): 활성화 상태인지 묻는 메소드. 작동 중이면 true, 아니면 false를 반환한다.(boolean값 반환)

 

1-7) 독립 스레드(Non Daemon)

Daed상태가 될 때까지 계속 실행되는 스레드

 

1-8) 종속 스레드(Daemon)

독립 스레드가 끝나면 같이 꺼지는 스레드

 

1-9) setDaemon: 종속 스레드로 설정

 

ThreadApp.java

package thread23;

//스레드로 구현하지 않은 클래스]
class NotThread{
	
	String title;

	public NotThread(String title) {
		this.title = title;
	}/////
	//스레드로 구현하지 않은 메소드
	void notThreadMethod() {
		/*아래 로직이 시간이 오래 걸리는 작업이라고 가정*/
		for(int i=1; i<=10;i++) {
			System.out.println(String.format("%s]i=%d", title,i));
			try {
				Thread.sleep(1000);
			}
			catch (InterruptedException e) {e.printStackTrace();}
			
		}
	}
}////////////////
//스레드로 구현한 클래스]
//1]Thread클래스를 상속받아 스레드 구현
class YesThread extends Thread{
	
	//인자 생성자:매개변수(인자)로 스레드명으로 사용할 문자열을 받는다
	public YesThread(String threadName) {
		super(threadName);
	}
	
	void ThreadMethod() {
		/*아래 로직이 시간이 오래 걸리는 작업이라고 가정*/
		for(int i=1; i<=10;i++) {
			System.out.println(String.format("%s]i=%d", getName(),i));
			try {
				//sleep(천분의 1초);
				//스레드를 1000분 1초동안 wait상태로 빠지게 하는 메소드.
			    //1000분 1초후에는 다시 Runnable상태로 자동으로 돌아감.
				sleep(1000);
			}
			catch (InterruptedException e) {e.printStackTrace();}
			
		}
	}
	
	/*
	 * 2]run()메소드 오버라이딩
	 * -run()메소드 안에 시간이 오래 걸리는 작업 기술
	 * -개발자가 직접 호출못하고 스래드가
	 * Running상태로 들어갔을때 자동으로 호출되는 메소드(콜백 메소드)
	 */
	
	@Override
	public void run() {
		ThreadMethod();
	}///////////////////
}
/*
독립 스레드(Non Daemon 스레드):
    메인스레드와 working스레드(개발자가 만든 스레드)
메인스레드가 끝나도 종료되지 않고 스레드가
Dead상태 될때까지 계속 실행되는 스레드

종속 스레드(Daemon 스레드):
모든 독립스레드가 끝나면 자동으로 종료(Dead)가 되는
스레드
-주 스레드의 보조역할을 하는 스레드
-종속 스레드는 주로 무한루프로 구성한다.
-예]배경음악 깐다든지,10분마다 자동 저장한다든지 등등..

어떤 스레드를 종속 스레드로 만들려면
setDaemon(true)로 설정
*/
//종속 스레드로 사용할 스레드]
//1]Thread 클래스 상속
class DaemonThread extends Thread{
	//2]run 메소드 오버라이딩
	@Override
	public void run() {
		while(true){
			System.out.println(String.format("%s]배경 음악이 흘려요", getName()));
			System.out.println(String.format("%s]3초마다 저장", getName()));
			try {
				sleep(3000);
			}
			catch (InterruptedException e) {e.printStackTrace();}
		}
	}/////////
}//////////////
public class ThreadApp {

	public static void main(String[] args) throws InterruptedException {
		System.out.println("main 스레드 시작");
		/*
		//스레드로 구현하지 않은 클래스 테스트]
		NotThread nt1=new NotThread("1st 클래스");
		NotThread nt2=new NotThread("2nd 클래스");
		nt1.notThreadMethod();
		nt2.notThreadMethod();*/
		//스레드로 구현한 클래스 테스트]
		YesThread yt1 = new YesThread("1st 스레드");
		YesThread yt2 = new YesThread("2st 스레드");
		yt1.setName("첫번째 스레드");
		yt1.start();//스레드를 Runnable상태로 전이시킴.run()메소드안의 코드가 실행되는것은 아님 
		//이건 start()하고 나서
		//yt1.join();
		/*
		  -join() 메소드
		   1]start()호출후에 join()메소드를 호출해야 한다
		   2]join()메소드를 호출한 스레드가
		   다 실행이 끝나야(Dead상태) 다른 스레드가 동작한다.
		 */
		/*
		   스레드의 우선권 설정]
		   우선순위가 높다고 그 스레드가
		   먼저 실행된다는 보장이 없다.(확률만 높을뿐)
		 */
		yt2.setPriority(Thread.MAX_PRIORITY);
		yt2.start();//runnable상태에 빠짐
		
		DaemonThread daemonThread = new DaemonThread();
		daemonThread.setName("Daemon Thread");
		daemonThread.setDaemon(true);//종속 스레드 설정
		daemonThread.start();
		
		System.out.println("현재 활성화 상태(Runnable 혹은 Running상태)에 있는 스레드 수:"+Thread.activeCount());
		System.out.println("첫번쨰 스레드 우선권:"+yt1.getPriority());//디폴트는 5 max는 10 min은 1의 값을 가짐
		System.out.println("두번쨰 스레드 우선권:"+yt2.getPriority());
		System.out.println("현재 실행중(Running)중인 스레드:"+Thread.currentThread().getName());
		System.out.println("main스레드의 우선권:"+Thread.currentThread().getPriority());
		System.out.println("main 스레드 끝");
		
	}///////main

}//////////class

 

2. Runnable인터페이스

Runnable인터페이스를 상속받으면 implement로 2개 상속받음

run()메소드 오버라이딩 해야 됨.

commander가 Thread를 상속받지 못했는데 

commander.start()는 start()메소드는 Thread에 있어서 사용불가

해결방법:

Runnable을 Thread타입으로 변환하고 싶어도 상속관계가 없어서 형변환 안된다

우회방법으로 Thread클래스의 인자 생성자를 이용해서

 Runnable을 상속받은 Commander의 객체(command)를 Thread타입으로 변환

ex) Thread th1 = new Thread(target); 로 생성

 

RunnableApp.java

package thread23;

class Soldier {
	void longedMethod() {
		for (int i = 1; i < 10; i++) {
			System.out.println(String.format("스레드명:%s,i=%d", Thread.currentThread().getName(), i));
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}////////////
//상속 받은 longMEthod()스레드로 구현하자
//1]Runnable인터페이스 상속
class Commander extends Soldier implements Runnable {
	//2]run()오버라이딩
	@Override
	public void run() {
		longedMethod();
	}
}
public class RunnableApp {

	public static void main(String[] args) {
		Commander commander = new Commander();
		System.out.println(commander instanceof Commander);
		System.out.println(commander instanceof Soldier);
		System.out.println(commander instanceof Runnable);
		//System.out.println(commander instanceof Thread);
		//commander.start();//[x]
		//Runnable타입을 Thread타입으로 변환.(상속관계가 없어서 형변환 불가)
		//Thread클래스의 인자 생성자를 이용해서 Thread타입으로 변환
		Thread th1=new Thread(commander);
		th1.start();
		Thread th2=new Thread(commander,"두번째 스레드");
		th2.start();
	}//////// main
}///////// class

sol)

 

 

3. 동기화(Synchronized)

 

정의: 공유 메모리를 여러 스레드가 동시에 사용하지 못하도록 하는 것 lock을 건다라고 볼 수 있음.

         하나의 스레드만이 공유 메모리를 참조하도록 제한하는 방법

 synchronized는 modifier이다

 

3-1) 메소드 동기화

여러 스레드에 의해 호출되는 공유 메소드를 동기화 함으로써 여러 스레드가 동시에 호출 못하도록 lock하는 것

여러 스레드가 공유하는 메소드를 가진 클래스와 인스턴스를 하나만 만든다. 

그리고 각 스레드에 인스턴스를 전달

package thread23;
/*
[형식]
	  접근지정자 synchronized  반환타입 메소드명([매개변수]){
	
	}
*/
class MethodSyncClass {
	int seed;

	public MethodSyncClass(int seed) {
		super();
		this.seed = seed;
	}////////
	//동기화시 synchronize 지정자 메소드에 추가]
	synchronized void increase(int inc) {
		for(int i=1;i<=10;i++) {
			seed+=inc;
			System.out.println(String .format("[스레드명:%s,데이타:%s,i=%s]",
					Thread.currentThread().getName(),seed,i));
			try {Thread.sleep(1000);}
			catch(InterruptedException e) {e.printStackTrace();}
		}
	}/////////
}/////////////
//[공유 메소드를 호출하는 스레드]
class MethodSyncThread extends Thread{
	//[멤버 변수]
	MethodSyncClass msc;
	//일정하게 증가시킬 숫자를 저장할 멤버(스레드마다 다르게 주자)
	int inc;
	//[인자 생성자]
	public MethodSyncThread(MethodSyncClass msc, int inc, String threadName) {
		super(threadName);
		this.msc = msc;
		this.inc = inc;
	}////////
	@Override
	public void run() {
		msc.increase(inc);
	}///////
}//////
public class MethodSynchronized {

	public static void main(String[] args) {
		//공유 메소드 갖고 있는 클래스,하나만 인스턴스화]
		MethodSyncClass msc= new MethodSyncClass(10);
		//두 개의 스레드 생성]
		MethodSyncThread mst1 = new MethodSyncThread(msc,2,"1st 스레드");
		mst1.start();
		MethodSyncThread mst2 = new MethodSyncThread(msc,5,"2nd 스레드");
		mst2.start();
	}//////// main
}//////// class

sol)

3-2) 데이터 동기화

 

동기화 안 한 결과: 동기화를 하지 않으면 각각의 스레드가 공유 데이터를 번갈아 가면서 증가시킨다

 

동기화한 결과: 1st스레드가 2씩 증가시킨 결과가 끝난 후, 2nd가 시작, 5씩 증가한 결과가 나타남

DataSynchronized.java

package thread23;
//동기화 블락을 이용한 데이타 동기화
//동기화 블락:
/*
synchronized(공유 메모리를 갖고 있는 객체){

	동기화할 로직

} 
*/
/* 여러 스레드가 공유하는 데이타(메모리)를 갖고 있는 클래스-인스턴스 하나만 만든다 
   각 스레드에 인스턴스를 전달한다 */
class DataSyncClass{
	int shareData;//여러 스레드가 공유하는 메모리
	public DataSyncClass(int shareData) {		
		this.shareData = shareData;
	}	
}///////////////////////////
/* 공유 데이타를 사용하는 스레드 */
class DataSyncThread extends Thread{
	//[멤버변수]
	//공유할 데이타를 갖고 있는 DataSynClass타입의 멤버
	DataSyncClass dsc;
	//일정하게 증가시킬 숫자를 저장할 멤버
	int inc;
	//[인자 생성자]	
	public DataSyncThread(DataSyncClass dsc, int inc,String threadName) {
		super(threadName);
		this.dsc = dsc;
		this.inc = inc;
	}
	//[오래 걸리는 메소드]
	//DataSynClass의 shareData에 저장된 값을 반복하면서
	//inc(증가분)만큼 누적해서 계속 저장.
	void increase() {
		for(int i=1;i<=10;i++) {
			dsc.shareData+=inc;
			System.out.println(String.format("[스레드명:%s,공유 데이타:%s]",
					getName(),dsc.shareData));			
			try {sleep(1000);} 
			catch (InterruptedException e) {e.printStackTrace();}
		}
	}//////////////////
	@Override
	public void run() {
		//데이타 동기화 전]
		//increase();
		//데이타 동기화  후]	
		synchronized (dsc) {
			increase();
		}
	}
	
}/////////////////////////////
public class DataSynchronized {

	public static void main(String[] args) {
		//공유 메모리 갖고 있는 클래스, 하나만 인스턴스화]
		DataSyncClass dsc = new DataSyncClass(10);
		//두 개의 스레드 생성]
		DataSyncThread dst1 = new DataSyncThread(dsc,2, "1st 스레드");
		dst1.start();
		DataSyncThread dst2= new DataSyncThread(dsc,5, "2nd 스레드");
		dst2.start();
		
	}///////////////main

}//////////////////class

기타) 음악 실행 코드

MediaPlayer.java

package thread23;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

import javazoom.jl.player.Player;

/*
 선행작업 :1.http://www.java2s.com/Code/Jar/j/Downloadjlayer101jar.htm에서 
         	Download jlayer-1.0.1-1.jar클릭하여 다운로드
         2.lib폴더 만든후 다운로드한 jar붙여넣기
           프로젝트 선택후 마우스 우클릭->Build Path->Configure Build Path->
           Libraries탭에서 Classpath선택후->Add External Jars후 lib폴더에 있는
           라이브러리 선택하여 클래스패스에 추가
         3.music폴더 만들어 mp3파일 저장
         
 */

public class MediaPlayer extends Thread {
	private Player player;
    private FileInputStream fis;
    String filePath;
    public MediaPlayer(String filePath) {    	
    	this.filePath=filePath;
    	
    }

    public void play() {
        try {     
        	fis = new FileInputStream(filePath);
            player = new Player(fis);           
            player.play();
        } 
        catch (Exception ex) {
            System.out.println("Error: " + ex.getMessage());
        }
    }

    public void playStop(){
        if (player != null) {
            player.close();
            player = null;
        }
    }
    
    @Override
    public void run() {    	
    	play();    		
    }
}

MediaPlayerApp.java

package thread23;


import java.io.FileInputStream;
import java.util.Scanner;

import javazoom.jl.player.Player;

public class MediaPlayerApp {	
	
	public static void main(String[] args) {
		
		Scanner  sc = new Scanner(System.in);
		MediaPlayer player=null;
		
		while(true) {
			System.out.println("1.음악 재생 2. 음악 중지 3. 프로그램 종료");
			System.out.println("메뉴번호를 입력하세요?");
			int menu=Integer.parseInt(sc.nextLine().trim());
			if(menu==1) {
				
				player = new MediaPlayer("music/music.mp3");
				player.start();
			}			
			else if(menu==2) {
				if(player !=null) player.playStop();
			}
			else if(menu==3) System.exit(0);
			else System.out.println("잘못 입력하셨습니다");
		}
	}///////////////
}

sol)