ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TIL #20 - Comparable, Exception, 스레드
    프로그래밍/TIL(국비과정) 2020. 4. 22. 20:42

     

    # Comparable

    사용자가 정의한 객체를 정렬할 때는 어떻게 정렬하라는 기준을 알려주지 않으면 안된다. 

    만약 apple과 banana가 들어있는 Fruits 객체에 대해 정렬을 하라고 했을 때 

    어떤 기준으로 정렬해야할지, 즉 객체들의 크기 관계를 명시해야한다. 

    Fruits 객체에 대해서는 이름 순으로 정렬할 수도 있고 과일의 각 재고수량 순서대로 정렬할 수도 있을 것이다. 

    그러기 위해선 클래스에 implements Comparable<Fruits> 를 해주면 된다. 

    그런 후 Comparable 인터페이스에 따른 메서드 compareTo를 오버라이드 해준다. 

    그런데 만약 이름 순으로도 정렬하고 싶고 재고수량 순으로도 정렬하고 싶다면 어떻게 할까? 

     

    하나의 객체 타입에 대해 2가지 이상의 기준으로 정렬 지원하려면 Comparator를 사용한다. 

    Comparator 인터페이스를 extends해 compare 메서드를 Overriding 하는 새로운 이름없는 클래스(익명클래스)를 정의한 후 그 클래스 객체를 생성해준다. 

    익명클래스는 말 그대로 클래스명이 없는 클래스이다. 

    아래와 같은 형태이다 

    Comparator<Fruits> nameComparator = new Comparator<Fruit>(){
    
        public int compare(Fruit fruit1, Fruit fruit2){
    
           return fruit1.name.compareTo(fruit2.name);
    
        }
    
    };
    
    Arrays.sort(fruits, nameComparator);

    위의 익명클래스를 원하는 조건에 따라 여러개 만들어주면 된다. 

    nameComparator는 Comparator 인터페이스를 implements 하는 어떤 class의 객체이다.

     

    #성적관리 프로그램

    성적관리 프로그램에 이름에 따른 오름차순, 총합에 따른 내림차순을 추가한다.

    먼저 메인메소드를 가지고 있을 뿐 인 메인클래스이다. 

    SungJukMain.java

    class SungJukMain {
    	public static void main(String[] args) {
    		SungJukAction sa = new SungJukAction();
    		sa.menu();
    	
    	}
    }
    

      각 메서드들이 있는 SungJukAction.java 에서 menu 메서드를 통해 기능을 구현할 것이기 때문에 미리 불러둔다. 

    필드는 아래와 같다. 

    SungJukDTO.java

    import java.text.DecimalFormat;
    
    class SungJukDTO implements Comparable<SungJukDTO>{
    	private int no;
    	private String name; 
    	private int kor, eng, math, tot;
    	private double avg; 
    	
    	// 생성자
    	public SungJukDTO(int no, String name, int kor, int eng, int math){
    		this.no = no; 
    		this.name = name; 
    		this.kor = kor; 
    		this.eng = eng; 
    		this.math = math;
    		// 총점, 평균같이 계산하는 데이터는 생성자로 받지 않아도 된다. 
    	}
    
    
    	// 총점, 평균
    	public void calc(){
    		tot = kor + eng + math; 
    		avg = (double)tot/3;
    	}
    	
    	
    
    	// getter
    	public int getNo(){
    		return this.no;
    	}
    	public String getName(){
    		return this.name; 
    	}
    	public int getKor(){
    		return this.kor; 
    	}
    	public int getEng(){
    		return this.eng; 
    	}
    	public int getMath(){
    		return this.math;	
    	}
    
    
    	@Override 
    	public String toString(){
    		return no +"\t"
    				+ name +"\t"
    				+ kor +"\t"
    				+ eng +"\t"
    				+ math +"\t"
    				+ tot +"\t"
    				+ new DecimalFormat("####.###").format(avg); // 소수 셋째자리까지 표현 
    	}
    
    	@Override 
    	public int compareTo(SungJukDTO o){
    		return this.tot < o.tot ? 1 : -1; // 총점으로 내림차순
    	}
    }
    

     Comparable 인터페이스를 받았고, 생성자를 통해 각 데이터들을 입력받는다. 

    총점이나 평균 같이 계산해서 나오는 데이터는 생성자로 받지 않고 따로 메소드를 만든다 

    그것이 calc() 이다. 

    평균은 실수값이기 때문에 double로 선언해준다. 

     

    toString() 메서드도 오버라이드 해준다. 

    toString() 메서드를 호출 했을 때 디폴트값인 객체의 주소값이 아닌 입력받은 값들을 리턴해주기 위함이다.

    평균의 경우엔 소수 셋째자리까지 표현하기 위해 DecimalFormat 클래스를 불러와 숫자 형태를 잡아준다. 


    DecimalFormat은 숫자 데이터를 원하는 형태로 표현하기 위한 숫자 형식 클래스이다. 

    new DecimalFormat("####.###").format(avg)

    위의 # 은 10진수로 소수 셋째자리부터 점을 찍는다는 것이다. 

    단, 빈자리는 채우지 않는다. 소수 둘째자리 수가 나와도 0을 붙인다던가 다른 문자를 붙혀 채우지 않는다는 뜻이다. 


     

    @Override 
    	public int compareTo(SungJukDTO o){
    		return this.tot < o.tot ? 1 : -1; // 총점으로 내림차순
    	}

    위의 compareTo 메서드는 SungJukDTO 값들을 비교하는 메서드이다. 

    this.tot < o.tot >> -1

    this.tot = o.tot >> 0

    this.tot > o.tot >> 1 

    위와 같은 결과가 나오며 위와 같은 결과가 나오면 내림차순 정렬이다. 

     

    이제 각종 메서드들이 들어 있는 SungJukAction.java 이다. 

    SungJukAction.java

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.Iterator;
    import java.util.Scanner;
    
    class SungJukAction {
    	Scanner scan = new Scanner(System.in);
    	private ArrayList<SungJukDTO> list = new ArrayList<SungJukDTO>();

    먼저 학생들의 정보들을 넣을 ArrayList를 선언한다. 

    ArrayList의 타입은 SungJukDTO로 해야 SungJukDTO의 필드에 포함되는 데이터들이 들어갈 수 있다. 

    ArrayList를 선언한는 방법은 위와 같은 방법 외에 아래와 같이도 할 수 있다. 

    public SungJukAction(){
    		list = new ArrayList<SungJukDTO>(); 
    	}

    아무거나 상관없다. 

     

    menu() 메서드다 

    public void menu() {
    		int choice;
    		while (true) {
    			System.out.println();
    			System.out.println("1~6 중에서 선택하세요");
    			System.out.println("*****************");
    			System.out.println("	1.입력");
    			System.out.println("	2.출력");
    			System.out.println("	3.검색");
    			System.out.println("	4.삭제");
    			System.out.println("	5.정렬");
    			System.out.println("	6.끝");
    			System.out.println("*****************");
    			System.out.println();
    			System.out.print("번호 : ");
    			choice = scan.nextInt();
    
    			// 나가는 것을 가장 먼저 설정해준다. 
    			if(choice == 6){
    				System.out.println("시스템을 종료합니다");
    				break; 
    			}
    		
    			if (choice == 1) {
    				insertArticle();
    			} else if (choice == 2) {
    				printArticle();
    			} else if (choice == 3) {
    				searchArticle();
    			} else if (choice == 4) {
    				deleteArticle();
    			} else if (choice == 5) {
    				sortArticle();
    			}// if
    		}// while
    	}// menu()
    

      메뉴 메서드는 간단하다. 

    1부터 6까지 수행할 메뉴를 보여주고 입력을 받는다. 

    그런데 if문을 보면, 프로그램을 나가는 6번 종료를 가장 먼저 if문으로 만들어주었다. 

    이 처럼 나가는 것을 가장 먼저 설정해주는 것이 좋다.

    6번 종료의 경우에는 갔다가 돌아오는 것이 아니고 그대로 나가버리기 때문에 else if를 해줄 의미가 없다. 

     

    또, 다 각자의 조건을 수행하기 때문에 각각 if문을 해주면 되는 것이 아닌가 하는 의문이 있을 수도 있지만

    if문을 연달아 쓰면 일일이 조건에 맞는지 묻기 때문에 불필요한 작업이 쌓여 프로그램이 약간 느려질 수도 있다. 

    그렇기 때문에 조건에 맞는 if문을 찾으면 그 아래의 조건들은 더 이상 살펴보지 않는 else-if문을 써주는 것이 좋다.

     

    입력에 대한 insertArticle() 이다. 

    번호를 입력받아 이미 있는 데이터인지 아닌지 검사한다. 

    public void insertArticle() {
    			int no;
    			System.out.print("번호입력 : ");
    			no = scan.nextInt();
                
                for(int i=0; i<list.size(); i++){
    			if(no == list.get(i).getNo()){
    				System.out.println("중복된 번호입니다");
    				return;
    				}
    			}

    list의 경우 배열과 다르게 length()가 아닌 size()를 사용해 list의 요소 수에 따라 for문을 돌린다 

    나중에 중복된 번호면 다시 번호입력을 유도하도록 while문을 추가할 예정이다. 

    지금은 중복된 번호임이 확인되면 코드를 나가게 되어있다. 

    return을 하면 메서드를 나갈 수 있다. 

    		System.out.print("이름  입력 : ");
    		String name = scan.next();
    		System.out.print("국어 입력 : ");
    		int kor = scan.nextInt();
    		System.out.print("영어 입력 : ");
    		int eng = scan.nextInt();
    		System.out.print("수학 입력 : ");
    		int math = scan.nextInt();

    데이터를 입력받는다. 

    		SungJukDTO dto = new SungJukDTO(no, name, kor, eng, math); // 생성자로 데이터 넣어주기 
    		dto.calc(); // 계산 메소드 가져온다. 
    		
    		list.add(dto); // 입력받은 dto 값들을 list에 넣는다.
    
    		System.out.println("입력 완료");
      }// insertArticle

    데이터를 입력받으면 입력받은 데이터들을 SungJukDTO의 생성자에 넣어준다. 

    그런 후 calc() 메서드를 이용해 총점과 평균을 계산 한 후 

    dto 값들을 list에 add() 해준다. 

     

    출력을 해주는 printArticle() 이다. 

    public void printArticle() {
    		System.out.println("출력");
    		System.out.println("번호\t\t이름\t국어\t영어\t수학\t총점\t평균");
    
    			for (SungJukDTO dto : list) {
    				System.out.println(dto); // 출력하기 
    			}// for
    	} // printArticle()

    출력은 간단하다. 

    입력받아 넣어둔 list를 확장형 for문을 이용해 출력해주면 된다. 

     

    이름에 따라 검색해주는 searchArticle() 이다. 

    사실 이름으로 데이터를 찾는 것은 좋은 방법이 아니다. 

    동명이인이 있을 수도 있기 때문에 이름과 같이 유일성이 없는 데이터보다는 번호와 같이 단 한 사람 만이 가지고 있는 데이터로 검색을 하는 것이 좋다. 

    하지만 일단 여기서는 이름으로 검색을 해본다.. 

     

    검색을 수행할 때는 검색에 따라 데이터가 있는 경우도 있고 없는 경우도 있다. 

    있는 경우는 그대로 그 데이터를 출력해주면 되고 없는 경우에는 없다는 메시지를 띄우면 된다. 

    // search
    	public void searchArticle(){
    		
    		int sw = 0; // 스위치 변수 
    		System.out.println();
    			System.out.print("찾을 이름을 입력하세요 : ");
    			String name = scan.next();
    
    			for(SungJukDTO dto : list){
    				if(name.equals(dto.getName())){
    					System.out.println(dto); // 출력하기 
    					sw = 1; // 찍었을 때 값을 변화시킨다. 
    				}
    			}// for 

     

    데이터를 찾고 못찾았고를 표시하기 위해 스위치 변수를 둔다.

    스위치 변수의 초기값은 0으로 데이터를 찾았을 때 값을 1이던 100이던 뭐던 변화시킨다. 

    만약 스위치변수가 그대로 0이면 데이터를 찾지 못한 것이므로 찾지 못했다는 메시지를 띄운다. 

    			if(sw == 0) System.out.println("찾고자하는 이름이 없습니다.");
    
    	}// searchArticle()

     

    데이터를 찾을 때는 확장형 for문을 돌려 list를 훑어본다. 

    만약, 사용자가 입력한 name과 dto에서 getName()으로 가져온 name과 같으면 해당 dto를 출력한다. 

    그리고 스위치 변수의 값을 변화시켜 데이터를 찾았음을 표시한다. 

     

    데이터를 삭제하는 delete() 이다. 

    	public void deleteArticle(){
    		System.out.println();
    		System.out.print("삭제할 이름을 입력하세요 : ");
    		String name = scan.next();

    삭제도 역시 이름을 데이터를 찾는다. 

    여기서도 스위치변수를 사용한다. 

     

    리스트들은 인덱스를 가지고 있다. 

    그렇기 때문에 만약 인덱스를 기준으로 삭제하게 되면 인덱스와 값이 들어맞지 않아 데이터가 엉뚱한 자리에 가있을 수도 있다. 

    따라서 Iterator를 사용한다. 

    Iterator는 인덱스를 무시하고 값을 하나씩 하나씩 건네 받는다. 

    Iterator<SungJukDTO> it = list.iterator();

    Iterator는 인덱스를 신경쓰지 않기 때문에 ArrayList와 같이 유동적인 리스트들과 함께 사용하기에 좋다. 

    while(it.hasNext()){ // 현재 위치에 항목이 있으면 T/ 없느면 F
    		SungJukDTO dto = it.next(); 
    			if(name.equals(dto.getName())){
    					it.remove(); 
    					sw = 1;
    				}
    	}// while

    이터레이터를 이용할 수 있는 메서드 총 세개를 이용하면 무사히 데이터를 삭제할 수 있다. 

    우선 hasNext()로 다음 데이터가 있는 동안은 계속 while문이 돌아가게 한다. 

    그런 후 next()를 이용해 데이터를 하나 꺼낸 후 다음 데이터로 간다. 

    remove()에서 꺼낸 데이터를 삭제한다. 

    이터레이터 자체가 next()로 값을 꺼내고 그 다음 값으로 가있기 때문에 

    remove()로 삭제시에 이미 이터레이터는 삭제할 값이 아닌 그 다음 값으로 옮겨가 있는 상태이다. 

    그렇기 때문에 삭제를 해도 아무런 문제가 없다. 

    삭제가 되었으면 스위치 변수 값에 변화를 주어 삭제가 완료됨을 표시한다. 

    	if(sw == 0){ 
    			System.out.println("찾고자하는 이름이 없습니다 ");	
    		}else {
    			System.out.println("데이터를 삭제하였습니다");	
    		}
    	}// deleteArticle()
    

      만약 스위치변수 값이 0 그대로면 삭제가 되지 않았다는 뜻이므로 찾고자하는 이름이 없다는 메시지를 띄운다.

    만약 0이 아니면 데이터가 무사히 삭제된 것이므로 데이터를 삭제했다는 메시지를 띄운다. 

     

    다음은 sortArticle()이다. 

    여기서는 총 2가지의 sort를 수행한다. 

    이름으로 오름차순, 총점으로 내림차순이다. 

    	public void sortArticle(){
    		while(true){
    			System.out.println("*****************");
    			System.out.println("1. 이름으로 오름차순");
    			System.out.println("2. 총점으로 내림차순");
    			System.out.println("3. 이전메뉴 ");
    			System.out.println("*****************");
    			System.out.println();
    
    			int choice = scan.nextInt();
    			if(choice == 3) break;

    총점으로 내림차순 하는 것은 이미 SungJukDTO.java에서 오버라이드 해서 그대로 불러와주기만 하면되고 

    이름으로 오름차순은 여기서 만들어야한다. 

    	if(choice == 1){
    			// 이름으로 오름차순
    			Comparator<SungJukDTO> com = new Comparator<SungJukDTO>(){
    				@Override
    				public int compare(SungJukDTO t1, SungJukDTO t2){
    					return t1.getName().compareTo(t2.getName());
    				}
    			};
    
    			Collections.sort(list, com);
    			printArticle();

    Comparator 인터페이스를 new 해주고 비교할 데이터타입 SungJukDTO를 제너릭타입으로 잡는다. 

    그런후 compare 메서드를 Override 해준다. 

    오름차순이므로 -1 0 1 순서가 된다. 

    t1.getName < t2.getName >> -1

    t1.getName = t2.getName >> 0

    t1.getName > t2.getNmae >> 1

    위와 같은 결과가 된다. 

     

    그런 다음 Collections를 불러 비교할 기준인 com도 함께 sort 한다. 

    총점으로 내림차순은 이미 만들었기 때문에 불러준다. 

    }else if(choice == 2){
    			// 총점으로 내림차순 
    			Collections.sort(list);
    			printArticle();
    		}
    		
    	} // while
    
    	}// sortArticle()
    	
    }
    

     

     

    #Exception 

    예외는 예기치 못한 error가 발생해 프로그램이 중도에 멈추는 것을 미리 예방하는 것이다. 

    이미 앞에서 System.in.read() 를 사용하면서 IOException을 throws 해준 적이 있다. 

    이와 같은 것이다. 

    System.in.read()를 사용하면서 IOException 처리를 해주지 않아도 사용하는데에는 문제 없지만 

    혹시나 발생할 문제를 미연에 방지하고자 Exception을 사용하는 것이다. 

     

    메인메소드의 String[] args에 값을 넣어본다. 

    ExceptionTest.java

    import java.util.Scanner;
    
    class ExceptionTest {
    	public static void main(String[] args) {
    
    		if(args.length > 0){ // 데이터가 하나 들어올 때 
    			System.out.println("args[0] = "+args[0]);
    		} 
    		System.out.println();
            
            
    		Scanner scan = new Scanner(System.in);
    		System.out.println("숫자입력 : ");
    		int num2 = scan.nextInt();
    

     

    먼저 try-catch 예외처리이다. 

    try 안에 오류 발생 가능성이 있는 코드를 넣고 

    catch 안에 오류가 발생하면 처리되는 부분을 넣는다. 

    그렇게 되면 오류가 발생할 경우 catch 안에 있는 코드가 실행된다. 

    물론 오류가 없으면 try 안의 코드가 무사히 실행된다. 

    	// try-catch
    		try{ // 이 안에서 오류가 발생할 가능성이 높다 
    			int num = Integer.parseInt(args[0]); 
    		
    			
    			System.out.print(num+ "/" + num2 + "=" + (num/num2))
    			
    		}catch(NumberFormatException e) {
    			System.out.println("숫자만 입력하세요"); 
    
    		// 0입력시 나오는 연산에러 
    		}catch(ArithmeticException e){
    			System.out.println("0이외의 숫자를 입력하세요");
    		}

    try 안에서 아까 입력받은 args[0]을 정수형으로 바꿔준다. 

    args 배열은 String 타입이기 때문이다. 

    그런데 만약 위에서 args[0] 값으로 양아무개 라고 입력하면 어떻게 될까. 

    NumberFormatException 이라는 오류가 난다. 

    이 오류는 양아무개는 정수형으로 바꿀 수 없다는 실행시 발생하는 오류인 것이다. 

    만약 이 오류를 NumberFormatException 메세지를 모르는 사람이 본다면 무척이나 당황스러울 것이다. 

    값은 사용자가 입력해야하기때문에 사용자가 보기에 알아들을 수 있는 메세지를 띄어주는 것이 좋을 것이다.

    그러므로 catch 안에 가서 NumberFormatException 오류가 발생했을 때 띄울 메세지를 입력한다. 

    사용자에게 숫자만을 입력할 수 있음을 알리는 것이다. 

     

    그 밑의 catch에 있는 오류는 0 입력시 나오는 연산에러다. 

    위에서 나누기 연산을 하고 있는데 정수형은 0으로 나눌 시 infinity 값이 나오기 때문에 0 입력을 지양하는 것이 좋다. 

    그렇기 때문에 이 catch 문에도 역시 0 이외의 숫자를 입력해 달라는 메시지를 띄운다. 

     

    try-catch 문에는 try 와 catch 이외에 finally 도 있다. 

    finally는 try문에서 코드가 잘 실행이 되어도, 오류를 만나 catch 문의 코드가 실행이 되어도 어찌됐든 반드시 실행되는 코드이다. 

    }finally { 
    					
    	System.out.println();
    	System.out.println("error가 있건 없건 무조건 실행 " );
    }

     

    Exception은 개발자가 직접 만들 수도 있다. 

    class MakeException extends Exception{ // 개발자가 만든 Exception 클래스 
    	private String errorMsg;

     Exception를 상속받고 오류 메시지를 띄우는 메서드를 만드는 것이다. 

    	public MakeException(){}
    
    	public MakeException(String errorMsg){
    		this.errorMsg = errorMsg;
    	}
    
    	@Override
    	public String toString(){
    		//return getClass() +":" + errorMsg;
    		// 클래스명 : 에러메세지 
    		return errorMsg; // 에러메세지만 나오게 
    	}
    	
    }
    

    toString 메서드를 오버라이드 해서 에러메시지가 뜨면 본인이 직접 설정한 return 값을 띄우게 만들면 된다. 

     

    위의 MakeException을 실제로 사용해본다. 

    아래는 구구단 프로그램이다. 

    import java.io.IOException;
    
    class ExceptionTest2 {
    	private int dan;
        
        // 입력
    	public void input() throws IOException{ // 구현부 
    		System.out.print("단 : ");	
    		dan = System.in.read()-'0'; // 1개의 문자로 입력받음 ex> A : 65 a : 97
    		// 문자열을 숫자형으로 바꿔주기 위해 -'0'
    	}

    구구단은 앞에서도 많이 했기 때문에 그냥 넘어가도록 한다. 

    단, dan을 System.in.read 로 받아주기 때문에 메서드에 throws IOException 을 해주었다. 

     

    출력 시에 구구단을 만들 때, 사용자로부터 받은 dan 이 2부터 9 사이의 정수여야하고 

    그 외의 값일 경우엔 경고 메시지를 띄우도록 한다. 

    	// 출력
    	public void output(){
    		if(dan >= 2 && dan <= 9){
    			for(int i = 1; i <= 9; i++){
    			System.out.println(dan + "*" + i + "=" + dan*i);	
    			}// for
    		}else{

      조건에 맞는 값을 받았다면 위와 같이 구구단을 실행하면 된다. 

    하지만 만약 그렇지 않다면 try-catch문을 사용해 에러메시지를 띄운다. 

    try{
    	throw new MakeException("숫자는 2부터 9까지만 가능" );
    	}catch(Exception e){
    	e.printStackTrace(); // 에러메시지가 찍힌다. 
    		}
    		
    	}
    }

     Exception이 발생했을 경우이다. 

    내가 원하는 범위에서 벗어나면 throw로 MakeException을 불러와준다. 

    throw는 앞에서 썼던 throws와는 다르다. 

    throws는 예외 및 오류를 잡는 쪽이고 throw는 에러를 발생시키는 쪽이다. 

    throws는 JVM에게 일을 떠넘긴다. 

    그런데 throw는 사용자에게 직접 Exception을 작성해 발생시킨다. 

     

    따라서, 우리는 MakeException을 직접 만들어 발생시키기 때문에 JVM에게 메시지를 떠넘기는 throws가 아닌 throw인 것이다. 

     

    public static void main(String[] args) throws IOException {
    		ExceptionTest2 et = new ExceptionTest2();
    		et.input(); // 호출 
    	
    		et.output();
    	}
    }
    

     

     

    #스레드 (Thread)

    스레드는 단위 프로그램이다. 

    main 메서드도 하나의 스레드이다. 

     

    # 멀티스레드

    만약 메인 메서드 안에 두개의 객체를 두고 

    두 객체에게 한명은 칼로 공격을 하게끔하고 

    한명은 팔로 방어를 하게끔 설정했다고 하자. 

    코드는 순서대로 아래와 같을 것이다. 

    main(~~)
    
        칼;
    
        공격;
    
    
    
        팔;
    
        방어();

    코드는 위에서 아래로 순서대로 실행된다. 

    그렇다면 과연 팔로 방어하는 사람은 제대로 방어를 했을까? 

    못했을 것이다. 

    위에서 칼로 공격을 실컷 다 한 후에 공격이 끝난 후에야 팔을 들어 방어를 했을 것이다. 

    우리가 원한 상황은 이것이 아니다. 

    어떤 사람이 칼을 들어 공격을 함과 동시에 다른 사람이 팔을 들어 방어를 해야한다. 

    그럼 다시 생각해본다. 

    칼을 들고 공격을 할 사람의 행동과 팔을 들고 방어를 할 사람의 행동을 각각 나눈 후

    0.1 초씩 잘라 0.1초 간격으로 왔다갔다 하게 하면 동시에 행동을 한 것 처럼 보일 것이다. 

    이와 같이 시간차로 프로그램들을 돌려주는 것이 멀티스레드 이다. 

     

    사실은 어느 시간 간격으로 왔다갔다하는 것인데 그 시간차에 따라 동시에 수행되는 것과 같이 보이는 것이다. 

    채팅 프로그램에서 주로 쓴다고 한다. 

     

    때문에 여러군데를 왔다갔다하는 특징으로 스레드는 흐름은 잡을 수 있지만 정확한 결과를 예측할 수 없다.

     

     # CPU가 과자를 골라먹는 프로그램

    과자 세개를 두고 CPU가 어느 과자를 어떤 순서로 먹는지 보자. 

    class SnackTest extends Thread{
    
    	public String str; 
    
    	public SnackTest(String str){
    		this.str = str;
    	}
        public void run(){
    		for(int i = 1; i <= 5; i++){
    			System.out.println(str + "\t i = "+ i+ "\t" + Thread.currentThread());
    			
    		}
    	}
    
    

     Thread는 두가지 방법으로 생성할 수 있다. 

    첫째가 클래스로 상속하는 것이고 두번째가 Runnable 인터페이스를 이용하는 것인데 

    스레드는 웬만하면 상속하지 말고 사용한다..

     스레드를 시작하는 것은 start() 메서드이다. 

    시작하면 스레드는 값들을 CPU에 넣는다. 

    그리고 CPU에서 스레드를 실행한다. run()

    여기서 일어나는 일들은 개발자나 사용자나 건들 수 없다. 

    CPU가 알아서 처리하기 때문이다. 

     

    위의 run()에서 Thread.currentThread()를 통해 현재 스레드가 누군지 각각 5번 출력한다. 

    그렇게 되면 과자 세가지가 동시에 처리된다. 

     

    이제 과자를 추가한다. 

    public static void main(String[] args) { 
    		// 스레드 생성 
    		SnackTest aa = new SnackTest("새우깡");
    		SnackTest bb = new SnackTest("포카칩");
    		SnackTest cc = new SnackTest("꼬북칩");
    		
    		// 스레드에 이름 부여 
    		aa.setName("새우깡");
    		bb.setName("포카칩");
    		cc.setName("꼬북칩");

    스레드를 생성하고 스레드에 이름을 부여했다. 

    이제 스레드를 시작한다. 

    	
    		// 스레드 시작 - 스레드 실행 (운영체제에 의해서 처리 : run() 콜)
    		aa.start(); 
    		try{
    			aa.join(); // 멈춘다 그렇기 떄문에 aa를 전부 다 실행하고 다른 스레드로 가야한다. 
    		}catch(InterruptedException e){
    			e.printStackTrace();
    		}
    		bb.start(); 
    		cc.start();
    		// 순서가 다 다르게 실행된다..
    	}
    }
    

    start()를 통해 스레드는 실행된다. 

    그렇게 되면 이제 스레드는 내 손을 떠나 CPU로 간 것이다. 

    이제 run()이 자동으로 불리게 된다. 

    결과를 보면 순서가 매번 다르게 출력되는 것을 볼 수 있다. 

     

    어떨 때는 스레드 중에 가장 먼저 처리되야하는 것들이 있을 수 있을 것이다. 

    그런 경우엔 우선순위를 부여할 수 있다. 

    우선순위를 부여하면 우선순위가 높은 스레드 먼저 CPU에 할당해주다. 

    이처럼 멀티스레드의 순서를 정하는 것을 스레드 스케줄링이라고 한다. 

    join()은 스레드가 멈출 때까지 기다리게 한다.

    그러면 aa 스레드를 끝날 때까지 기다렸다가 다음으로 넘어간다. 

     

    우선순를 부여하는 방법은 아래와 같다.

    우선순위는 1부터 10까지이다. 

    		// 우선순위 1_~10
    		aa.setPriority(Thread.NORM_PRIORITY); // 기본값 5
    		bb.setPriority(Thread.MIN_PRIORITY); //1
    		cc.setPriority(Thread.MAX_PRIORITY); // 10
    		

     

    댓글

Designed by Tistory.