즐겁게 하하하 2022. 1. 31. 08:17
728x90

 


★ 프로세스 : 실행중인 프로그램
★ 쓰레드 : 실제 작업을 수행 , 모든 프로세스는 최소한 하나의 쓰레드를 가지고 있다.
   thread는 각각 자신만의 작업 공간을 가짐 ( context )
   각 thread 사이에서 공유하는 자원이 있을 수 있음 (자바에서는 static instance)
   동기화( 일종의 순차적 수행)를 구현하지 않으면 오류가 발생할 수 있음
    __________________________________________________________
   쓰레드 -> 실행 가능한 상태로 만듬 -> OS 스케줄러가 실행 순서를 결정
   쓰레드는 사용자 쓰레드와 데몬 쓰레드(보조 쓰레드) 가 있는데, 
     실행중인 사용자 쓰레드( main 등.. )가 하나도 없을때 프로그램은 종료된다.
     main 메서드의 쓰레드가 종료되었다고 프로그램이 종료 되는것이 아니다.
    __________________________________________________________
    단점 : 1. 동기화에 주의해야 한다.
           2. 교착상태가 발생하지 않게 주의해야 한다.
           3. 각쓰레드가 효율적으로 고르게 실행될 수 있게 해야한다. : 기아 발생 주의..
    __________________________________________________________
    1. Thread 클래스 상속하여 만들기
    2. Runnable 인터페이스 구현하여 만들기 ( 이미 다른걸 extends 한 경우 사용 )

	class Mythread extends Thread{ 
		public void run() {  
			for (int j = 0; j <= 5; j++) { System.out.print(j + "\t"); }
		} 
	}

	public class ThreadTest { 
		public static void main(String[] args) {
           //Thread[main(호출한곳) , 5(우선순위) , main(쓰레드 그룹)]
			System.out.println( Thread.currentThread() + " start");  
			Mythread th1 = new Mythread();
			Mythread th2 = new Mythread(); 
			th1.start();
			th2.start();
			System.out.println( Thread.currentThread() + " end"); 
		} 
	}
    ======================================
	class Mythread2 implements Runnable{  
		public void run() {  
			for (int j = 0; j <= 5; j++) { System.out.print(j + "\t"); }
		} 
	}

	public class ThreadTest2 { 
		public static void main(String[] args) {
			System.out.println( Thread.currentThread() + " start");  
			Mythread2 runnable = new Mythread2(); 
			Thread th1 = new Thread(runnable); 
			Thread th2 = new Thread(runnable); 
			th1.start();
			th2.start(); 
			System.out.println( Thread.currentThread() + " end"); 
		} 
	}
    ======================================
	class Mythread3 implements Runnable{  
		public void run() {  
			for (int j = 0; j <= 5; j++) { System.out.print(j + "\t"); }
		} 
	}

	public class ThreadTest3 { 
		public static void main(String[] args) { 
			System.out.println( Thread.currentThread() + " start");   
			Runnable run = new Runnable() { 
				@Override
				public void run() { System.out.println("=== run ===");  } 
			}; 
			run.run(); 
			System.out.println( Thread.currentThread() + " end"); 
		} 
	}
   __________________________________________________________
   A. synchronized( 동기화 )
     - sleep(시간) <=> 시간이 지나면
     - wait()      <=> notify() : not Runnable 상태 Thread중 하나 깨어남
                       notifyAll() : not Runnable 상태 Thread 모두 깨운다
                       notifyAll() 메서드의 사용을 권장한다.
     - join()      <=> 지정한 쓰레드 작업이 모두 끝날때까지
       ㄴ join을 걸면 자신이 not Runnable 상태에 빠지고, 지정된 쓰레드 
          작업(Runnable 상태가됨) 이 끝나면 Runnable 상태로 되돌아 온다

   	B. Lock , Condition 클래스 를 이용한 임계영역 설정 및 동기화 
       - ReentrantLock 클래스 
         . 재진입 가능한 lock, 가장 일반적인 배타 lock 
         . 특정 조건에서 lock을 풀고, 나중에 다시 lock을 얻어 임계영역으로 진입 가능 
         awit()        ==  wait() 
         signal()     ==  notify()  
         signalAll() ==  notifyAll()  

       - ReentrantReadWriteLock 클래스 
        . 읽기를 위한 lock과 쓰기를 위한 lock을 따로 제공 
        . 읽기에는 공유적이고, 쓰기에는 배타적인 lock 
        . 읽기 lock이 걸려있는 경우, 다른 스레드들도 읽기라면 진입 가능 (read-only) 
        . 읽기 lock이 걸려있는 경우, 다른 스레드가 쓰기 lock은 금지 (데이터 변경 방지) 

       - StampedLock 클래스  
        . ReentrantReadWriteLock에 낙관적인 lock의 기능을 추가 
        . 낙관적인 읽기 lock은 쓰기 lock에 의해 바로 해제 가능 
        . 무조건 읽기 lock을 걸지 않고, 쓰기와 읽기가 충돌할 때만 쓰기 후 읽기 lock 

       - Condition 클래스 
        . 특정 스레드를 위한 Conditon 인스턴스를 만들어 스레드 별로 
          wait pool을 만들어주는 기능을 한다. 
        . 이를 이용하여 깨우고 싶은 특정 스레드만 깨우는 것이 가능하다.
   __________________________________________________________
   쓰레드의 우선순위( 희망사항 :: 결정은 OS )
   - 중요도에 따라 우선순위를 다르게 하여 특정 쓰레드가 더 많은 작업시간을 갖게 할 수 있다.
   - 우선순위가 높을수록 빨리 끝날 가능성이 높아진다.
     public static final int MAX_PRIORITY = 10  :: 최대 우선순위
     public static final int MIN_PRIORITY = 1   :: 최소 우선순위
     public static final int NORM_PRIORITY = 5  :: 보통 우선순위

     void setPriority( int newPriority ) :: 쓰레드의 우선순위를 지정한 값으로 변경
     int getPriority() :: 쓰레드의 우선순위를 반환
   __________________________________________________________
   쓰레드의 그룹
    - 서로 관련된 쓰레드를 그룹으로 묶음
    - 모든 쓰레드는 반드시 하나의 그룹에 포함되야 한다.
      그룹을 지정하지 않으면 main 쓰레드 그룹에 속해진다.
    - 자신을 생성한 쓰레드( 부모 )의 그룹과 우선순위(기본5)를 상속받는다.
      Thread( ThreadGroup group , String name )
      Thread( ThreadGroup group , Runnable target )
      Thread( ThreadGroup group , Runnable target , String name )
      Thread( ThreadGroup group , Runnable target , String name , long stackSize )

      ThreadGroup getThreadGroup(); :: 쓰레드 자신이 속한 그룹 반환
      void uncaughtException( Thread t , Throwable e ) 
       :: 처리되지 않은 예외에 의해 그룹의 쓰레드가 실행이 종료 되었을때 JVM에 
          의해 이 메서드가 자동으로 호출됨
   __________________________________________________________     
   싱글 쓰레드 구현 
    - Thread 클래스 상속 ( 자바는 단일상속 ) 
      Class MyThread extends Thread{ 
        public void run() {   // 오버라이딩 
         //작업 내용
         System.out.println( this.getName() ); :: 조상Thread의 getName() 호출
         }
      }
      MyThread t1 = new MyThread(); 
      t1.start(); // 실행
   __________________________________________________________
   Runnable 인터페이스를 구현 
     Class MyThread implements Runnable {  
       public void run(){  // 추상메서드run
          //작업내용 
          // Thread.currentThread() :: 현재 실행중인 Thread 반환
          System.out.println( Thread.currentThread().getName() );
       } 
     } 

     Runnable r = new MyThread();
     Thread t1 = new Thread(r) 
     :: Thread t1 = new Thread( new MyThread() ) 
     t1.start(); // 실행
   __________________________________________________________
   멀티 쓰레드 구현 ( 실행순서는 OS가 결정한다. )
     - t1 과 t2 문맥교환(context Switching) 이 발생하여 단일 쓰레드 보다 시간이 더 걸린다.
      MyThread1 t1 = new MyThread1(); 
      MyThread2 t2 = new MyThread2();
      t1.start(); 
      t2.start();

     - 쓰레드의 I/O 블락킹( 입출력시 작업중단 )
       사용자 입력을 받기 전까지 프로세스가 대기상태가 된다.
       이 대기 상태가 되는동안 멀티 Thread를 이용하여 다른 작업을 수행 할 수 있다.      
   __________________________________________________________
   데몬 쓰레드
    - 일반 쓰레드의 작업을 돕는 보조 역할
    - 일반 쓰레드가 모두 종료되면 자동 종료
    - 가비지 컬렉터 , 자동저장, 화면 자동 갱신 등에 이용
    - 무한루프와 조건문을 이용해 실행후 대기하다가 특정조건이 만족되면 작업수행후 대기.
    - boolean isDaemon() 데몬 쓰레드 인지 확인 :: 데몬이면 true
    - void setDaemon( boolean on) 
      :: 데몬 또는 사용자 쓰레드로 변경 true 로 지정시 데몬 쓰레드
      :: 반드시 start 되기 전에 호출 되어야 한다.
 __________________________________________________________
    쓰레드 상태
     NEW   :  쓰레드가 생성되고 아직 START 호출안됨
     RUNNABLE : 실행중, 실행가능한 상태
     BLOCKED : 동기화 블럭에 의해 일시정지 된 상태
     WAITING : 종료 되지는 않았지만 실행 가능하지 않은 일시 정지 상태
       ㄴ TIMED_WAITING : WAITING 에서 일시 정지 시간이 지정된 경우
     TERMINATED  : 작업이 종료된 상태
  __________________________________________________________
  소요 시간 구하는 코드
  long startTime = System.currentTimeMillis(); 
    for(int i=0; i < 300; i++)
        System.out.printf("%s", new String("-"));	 
    System.out.print("소요시간1:" +(System.currentTimeMillis()- startTime)); 
  __________________________________________________________       
   Thread 종료하기
    - Thread를 종료할 때 사용함
    - 무한 반복의 경우 while(flag)의 flag 변수값을 false로 바꾸어 종료를 시킴
 
쓰레드 이름
this.getName()
Thread.currentThread().getName()
다른 쓰레드 기다리기
join() :: 작업이 모두 끝날때까지
join( long millis ) :: 지정된 시간동안 1/1000


Mythread th1 = new Mythread();
th1.start();
try {
  th1.join(); // main쓰레드가 th1의 작업이 끝날 때까지 기다린다.
} catch( InterruptedException e ) { }


Exception의 자손으로 InterruptedException
예외처리 필수
시간 1초간 잠자기 1/1000
static void sleep() :: 쓰레드 자신에게만 가능


void delay ( millis ){
   try{
      Thread.sleep( millis );
      //Thread.sleep(1000);
   } catch ( InterruptedException e ) { }
}


Exception의 자손으로 InterruptedException
예외처리 필수
sleep 또는 join 에 의해 일시 정지
상태인 쓰래드 깨우기
* ex) file 다운로드 중단 , 재개
void interrupt() :: interrupted상태를 false 에서 true로 변경
boolean isInterrupted() :: interrupted상태를 반환
static boolean interrupted(0 :: 현재 쓰레드의 interrupted 상태
를알려주고 false로 초기화
Thread.interrupted();
남은 시간을 다음 쓰레드에게 양보하고
자신은 실행 대기 한다.
interrupt()와 yield()를 적절히 사용하면
응답성과 효율을 높일 수 있다.
static void yield();
Thread.yield();
ㅡ os 스케줄러 한테 통보만 하는것이지 선택은 os가 한다.
즉시 종료
stop() :: 교착상태 빼지기 쉬워서 deprecated 됨
일시정지
suspend() :: 교착상태 빼지기 쉬워서 deprecated 됨
suspend 일시정지 해제
resume() :: 교착상태 빼지기 쉬워서 deprecated 됨
우선순위 정하기
순위 불러오기
MyThread th = new MyThread();
th.setPriority(7); // 우선순위7로
System.out.println( th.getPriority() );

 

Thread 종료 / 멀티 Thread synchronized
import java.util.ArrayList;
class Fastibrary{
	public ArrayList<String> self = new ArrayList<String>(); 
	public Fastibrary() {
		self.add("책1"); self.add("책2"); 
	}
	
	public synchronized String lendBook() throws InterruptedException { 
		Thread t = Thread.currentThread();	
		while(self.size() == 0) {
			System.out.println(t.getName() + " Waiting");
			wait();
			System.out.println(t.getName() + " Waiting End");
		}
		
		if( self.size() > 0 ) {
			String book = self.remove(0);
			System.out.println(t.getName() + " : " + book + " lend");
			return book;
		}else { return null; }
	}
	
	public synchronized void returnBook( String book ) { 
		Thread t = Thread.currentThread();
		self.add(book);
		notify();
		System.out.println(t.getName() + " : " + book + " return");
	} 
}

class Student extends Thread{ 
	public Student(String string) { super.getName(); }

	public void run() { 
		try {
			String title = LibraryMain.library.lendBook();
			if(title == null) { return; }
			sleep(200);
			LibraryMain.library.returnBook(title);
		} catch (InterruptedException e) { 
			e.printStackTrace();
		}  
	} 
}

public class LibraryMain { 
	public static Fastibrary library = new Fastibrary();
	public static void main(String[] args) {
		  
		Student th1 = new Student("th1");
		Student th2 = new Student("th2");
		Student th3 = new Student("th3");
		Student th4 = new Student("th4");
		th1.start();  th2.start();
		th3.start();  th4.start();
	}
} 
## ================ 데몬 쓰레드 ================
class Ex13_7 implements Runnable  {
	static boolean autoSave = false;

	public static void main(String[] args) {
		Thread t = new Thread(new Ex13_7());
		t.setDaemon(true);		// 이 부분이 없으면 종료되지 않는다.
		t.start();
        t.isInterrupted(); // true;
        // t.interrupted(); :: 잘못된 입력.. static 메소드는 자신 Thread 호출
        Thread.interrupted(); :: Main 쓰레드 interrupted상태를 알려주고 false로 초기화

		for(int i=1; i <= 10; i++) {
			try{
				Thread.sleep(1000);
			} catch(InterruptedException e) {}
			System.out.println(i);

			if(i==5) autoSave = true;
		} 
		System.out.println("프로그램을 종료합니다.");
	}

	public void run() {
		while(true) {
			try { 
				Thread.sleep(3 * 1000); // 3초마다
			} catch(InterruptedException e) {}

			// autoSave의 값이 true이면 autoSave()를 호출한다.
			if(autoSave) autoSave();
		}
	}

	public void autoSave() {
		System.out.println("작업파일이 자동저장되었습니다.");
	}
}
 
## ================ 쓰레드 volatile  ================
class Ex13_10 {
	public static void main(String args[]) {
		 
		MyThread th1 = new MyThread("*");
		MyThread th2 = new MyThread("**");
		MyThread th3 = new MyThread("***");
		th1.start(); th2.start(); th3.start();

		try {
			Thread.sleep(2000);
			th1.suspend();	// 쓰레드 th1을 잠시 중단시킨다.
			Thread.sleep(2000);
			th2.suspend();
			Thread.sleep(3000);
			th1.resume();	// 쓰레드 th1이 다시 동작하도록 한다.
			Thread.sleep(3000);
			th1.stop();		// 쓰레드 th1을 강제종료시킨다.
			th2.stop();
			Thread.sleep(2000);
			th3.stop();
		} catch (InterruptedException e) {}
	} // main
}

class MyThread implements Runnable {
	volatile boolean stopped = false;   
	volatile boolean suspended = false;
   
    ## Multi Thread환경에서 Thread가 변수 값을 읽어올 때 각각의 CPU Cache에 저장된 값이
       다르기 때문에 변수 값 불일치 문제가 발생하게 됩니다.
       volatile 키워드를 추가하게 되면 Main Memory에 저장하고 읽어오기 때문에 
       변수 값 불일치 문제를 해결 할 수 있습니다.
    ## Multi Thread 환경에서 하나의 Thread만 read & write하고 
       나머지 Thread가 read하는 상황에서 가장 최신의 값을 보장합니다.

	Thread th;
	MyThread( String name ){
		th = new Thread( this , name ); // Thread( Runnable r , String name )
	}
	
	void start() {  th.start(); }	
	void stop() { stopped = true; }	
	void suspend() { suspended = true; }	
	void resume() { suspended = false; }
	
	public void run() {
		while(!stopped) {
			if(!suspended) {
				System.out.println(Thread.currentThread().getName());
				try {
					Thread.sleep(1000);
				} catch(InterruptedException e) {}
			}
		}
	} // run()
}
 
## ================ 쓰레드 synchronized , wait() , notify() ================
import java.util.ArrayList;
class Customer2 implements Runnable { // 손님
	private Table2  table;
	private String food;

	Customer2(Table2 table, String food) {
		this.table = table;  
		this.food  = food;
	}

	public void run() {
		while(true) {
			try { Thread.sleep(100);} catch(InterruptedException e) {}
			String name = Thread.currentThread().getName();
			table.remove(food);
			
			System.out.println(name + " ate a " + food);
		} // while
	}
}
 
class Cook2 implements Runnable { // 요리사
	private Table2 table;
	
	Cook2(Table2 table) { this.table = table; }

	public void run() {
		while(true) {
			int idx = (int)(Math.random()*table.dishNum());
			table.add(table.dishNames[idx]);
			try { Thread.sleep(10);} catch(InterruptedException e) {}
		} // while
	}
}
 
class Table2 {
	String[] dishNames = { "donut","donut","burger" }; // donut의 확률을 높인다.
	final int MAX_FOOD = 6;
	private ArrayList<String> dishes = new ArrayList<>();

	public synchronized void add(String dish) {
		while(dishes.size() >= MAX_FOOD) {
				String name = Thread.currentThread().getName();
				System.out.println(name+" is waiting.");
				try {
					wait(); // COOK쓰레드를 기다리게 한다.
					Thread.sleep(500);
				} catch(InterruptedException e) {}	
		}
		dishes.add(dish);
		notify();  // 기다리고 있는 CUST를 깨우기 위함.
		System.out.println("Dishes:" + dishes.toString());
	}

	public void remove(String dishName) {
		synchronized(this) {	
			String name = Thread.currentThread().getName();

			while(dishes.size()==0) {
					System.out.println(name+" is waiting.");
					try {
						wait(); // CUST쓰레드를 기다리게 한다.
						Thread.sleep(500);
					} catch(InterruptedException e) {}	
			}

			while(true) {
				for(int i=0; i<dishes.size();i++) {
					if(dishName.equals(dishes.get(i))) {
						dishes.remove(i);
						notify(); // 잠자고 있는 COOK을 깨우기 위함 
						return;
					}
				} // for문의 끝

				try {
					System.out.println(name+" is waiting.");
					wait(); // 원하는 음식이 없는 CUST쓰레드를 기다리게 한다.
					Thread.sleep(500);
				} catch(InterruptedException e) {}	
			} // while(true)
		} // synchronized
	}
	public int dishNum() { return dishNames.length; }
}
 
class Ex13_15 {
	public static void main(String[] args) throws Exception {
		Table2 table = new Table2();
        
		 // 쓰레드 그룹 Thread( ThreadGroup group , String name )
		new Thread(new Cook2(table), "COOK").start();
		
		new Thread(new Customer2(table, "donut"),  "CUST1").start();
		new Thread(new Customer2(table, "burger"), "CUST2").start();
		Thread.sleep(2000);
		System.exit(0);
	}
}
 
## ================ 쓰레드 synchronized , wait() , notify() ================
import java.util.ArrayList;
class Customer2 implements Runnable { // 손님
	private Table2  table;
	private String food;

	Customer2(Table2 table, String food) {
		this.table = table;  
		this.food  = food;
	}

	public void run() {
		while(true) {
			try { Thread.sleep(100);} catch(InterruptedException e) {}
			String name = Thread.currentThread().getName();
			table.remove(food);
			
			System.out.println(name + " ate a " + food);
		} // while
	}
}
 
class Cook2 implements Runnable { // 요리사
	private Table2 table;
	
	Cook2(Table2 table) { this.table = table; }

	public void run() {
		while(true) {
			int idx = (int)(Math.random()*table.dishNum());
			table.add(table.dishNames[idx]);
			try { Thread.sleep(10);} catch(InterruptedException e) {}
		} // while
	}
}
 
class Table2 {
	String[] dishNames = { "donut","donut","burger" }; // donut의 확률을 높인다.
	final int MAX_FOOD = 6;
	private ArrayList<String> dishes = new ArrayList<>();

	public synchronized void add(String dish) {
		while(dishes.size() >= MAX_FOOD) {
				String name = Thread.currentThread().getName();
				System.out.println(name+" is waiting.");
				try {
					wait(); // COOK쓰레드를 기다리게 한다.
					Thread.sleep(500);
				} catch(InterruptedException e) {}	
		}
		dishes.add(dish);
		notify();  // 기다리고 있는 CUST를 깨우기 위함.
		System.out.println("Dishes:" + dishes.toString());
	}

	public void remove(String dishName) {
		synchronized(this) {	
			String name = Thread.currentThread().getName();

			while(dishes.size()==0) {
					System.out.println(name+" is waiting.");
					try {
						wait(); // CUST쓰레드를 기다리게 한다.
						Thread.sleep(500);
					} catch(InterruptedException e) {}	
			}

			while(true) {
				for(int i=0; i<dishes.size();i++) {
					if(dishName.equals(dishes.get(i))) {
						dishes.remove(i);
						notify(); // 잠자고 있는 COOK을 깨우기 위함 
						return;
					}
				} // for문의 끝

				try {
					System.out.println(name+" is waiting.");
					wait(); // 원하는 음식이 없는 CUST쓰레드를 기다리게 한다.
					Thread.sleep(500);
				} catch(InterruptedException e) {}	
			} // while(true)
		} // synchronized
	}
	public int dishNum() { return dishNames.length; }
}
 
class Ex13_15 {
	public static void main(String[] args) throws Exception {
		Table2 table = new Table2();
        
		 // 쓰레드 그룹 Thread( ThreadGroup group , String name )
		new Thread(new Cook2(table), "COOK").start();
		
		new Thread(new Customer2(table, "donut"),  "CUST1").start();
		new Thread(new Customer2(table, "burger"), "CUST2").start();
		Thread.sleep(2000);
		System.exit(0);
	}
}
 

 

 

728x90