KH/JAVA

# 28 Thread (쓰레드)

오늘의 진 2022. 7. 20. 14:18

Thread (쓰레드)

 

컴퓨터는 실행할 프로그램을 메모리에 load 시켜놓고 CPU가 명령어를 하나씩 해석하면서 처리한다.

이때 하드디스크와 같은 보조 기억 장치에 저장되어 있는 프로그램을 메모리에 저장하게 된다. 

하드 디스크에 들어 있는 프로그램을 파일이라고 부르는 반면에 메모리에 적재된 프로그램을 process(프로세스)라고 부른다.

 

Thread는 프로세스 안에서 순차적으로 작동하는 명령어의 집합이다.

순차적으로 실행되는 명령어들은 하나의 실로 꿸수 있기 때문에 Thread(실)이라고 부른다.

어떤 프로그램(프로세스)에서는 여러개의 명령어가 독립적으로 실행되는 경우가 있는데

이런경우를 다중 스레드라고 한다.

즉 Multi - Thread(다중스레드)는 한개의 프로세스에서 독립적으로 처리되는 작업이 여러개 있는 경우이다.

 

자바에서 스래드는 java.lang.Thread 클래스에서 상속 받아 사용한다.

스레드를 사용하는 경우의 대부분은 동시에 두가지 이상의 작업을 처리할때이다.

 

프로세스는 특정한 작업을 처리하기 위해 메모리에 적재되어있는 프로그램이고 스레드는 명령의 제어흐름이다.

즉 명령어가 실행되는 순서의 흐름이다.

스레드는 프로세스 내부에서 독립적인 작업을 처리하는 명령어의 집합이다. 

 

< 스레드의 사용방법 >

: 자바는 스레드를 사용하기 위해 두가지 방법을 제공한다.

 

1. java.lang.Thread 클래스를 상속받아 스레드를 생성하여 사용하는 방법

class  클래스이름   extends Thread{
             .............. ;
}

 

2. java.lang.Runnable 인터페이스를 implements 해서 스레드를 생성하는 방법

class   클래스이름   extends 슈퍼클래스  implements Runnable{
                  ..................... ;
}

 

 

 

Thread class 사용(java.lang.Thread 클래스를 상속받아 스레드를 생성하여 사용하는 방법)

1. Thread 클래스를 상속 받은 클래스를 선언

 

2.Tread 클래스의 추상메소드인 run() 메소드를 재정의한다.(안적어도 에러는 안남)

 

  class ThreadTest extends Thread {
       public void run( )   { >>run() 메소드 재정의
                     ............. ;
        }
}

3. main() 메소드에서 Thread 클래스를 상속 받은 클래스 객체를생성한다.

 

4.생성된 객체를 사용해 start( ) 메소드를 호출한다. (start를 호출하면 run메소드가 동작한다)

public static void main(String[] args){
   ThreadTest obj = hew ThreadTest();   // 객체 생성
   obj.start();  //start  메소드 호출
   }

 

 

(예시)

class MyThread extends Thread {

	public void run() {

		try {
			for (int i = 0; i < 20; i++) {
				Thread.sleep(1000); // 1초를 멈추게함
				System.out.println(" " + i + "*" + i + "=" + i * i);
			}
		} catch (InterruptedException e) { // Thread.sleep을 쓰기위한 예외처리
			e.printStackTrace();
			System.out.println(e.getMessage());

		}

	}

}

public class ThreadTest_1 {
	public static void main(String[] args) {

		MyThread thread = new MyThread();

		// thread.start(); // thread(스레드)를 동작시켜라는 뜻
		thread.run(); // run이라는 메소드를 동작시키라는 뜻

	}

}

 

(예시) - run메소드 실행과 start로 스레드 동작시키는것 차이점

class MyThread_2 extends Thread {
	public void run() {

		try {

			for (int i = 0; i < 20; i++) {
				Thread.sleep(1000);
				System.out.println(" " + i + " 번 쓰래드 ^^^");
				
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}
}

public class ThreadTest_2 {
	public static void main(String[] args) {
            //main도 스레드이다. 
		MyThread_2 thread_1 = new MyThread_2();
		thread_1.run();   //run메소드가 호출된 다음에 main의 코드가 실행됨
		//thread_1.start(); //스레드는 내부적으로 돌고있음으로 나오는 순서는 랜덤이다.

		try {

			for (int i = 0; i < 20; i++) {
				Thread.sleep(1000);
				System.out.println(" " + i + " main 쓰래드 ***");
				;
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}

}

 

 

 

 

 

 

class MyThread_3 extends Thread {

	public MyThread_3(String name) {
		super(name); //전달된 것이 Tread의 getName() 메소드로 넘어감 
	}

	@Override
	public void run() {
		try {
			for (int i = 0; i < 5; i++) {
				Thread.sleep(1000);
				System.out.println(" " + i + " 번" + getName() + ": 스레드");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();

		}
	}

}

public class ThreadTest_3 {
	public static void main(String[] args) {

		MyThread_3 obj_1 = new MyThread_3("첫번째");
		MyThread_3 obj_2 = new MyThread_3("두두번째");
		MyThread_3 obj_3 = new MyThread_3("세세세번째");

		obj_1.start(); // 스레드 호출 -뒤죽박죽나옴 obj_123이 동시에 돌아감
		obj_2.start();
		obj_3.start();
		
		
//		obj_1.run(); //메소드 호출 -순서대로나옴 obj_123이 순서대로 출력됨
//		obj_2.run();
//		obj_3.run();

	}

}

 

 

 

 

 

 

 

Thread 에서 run( ) 대신 start( ) 를 호출하는 이유

 

스레드를 실행할때 직접 run( ) 메소드를 호출하지 않고 start( ) 메소드를 호출하는이유가 있다. 

프로세스나 스레드가 실행된다는 의미는 CPU를 사용해 작업을 수행하는 것을 뜻한다.

그런데 한번에 한가지 작업만 처리하는 것이 아니고 동시에 2가지 이상의 작업을 처리하는 멀티 프로세싱을 위해서는 

CPU 스케줄링이 필요합니다. 운영체제는 보다 효율적으로 사용하기 위해 작업의 우선순위를 정해 처리한다. 

결국 CPU의 사용권한을 정하는 것은 프로그램이 아니라 운영체제의 몫이다.

start( ) 메소드는 스레드를 실행가능한 상태 즉 스래드가 CPU를 사용할 수 있도록 준비 상태를 만드는 역할을 한다.

그리고 CPU 스케줄러에 의해 run( ) 메소드를 호출해 실제 CPU를 사용하는 상태로 바뀐다. 

 

 

(예제)

import javax.swing.JOptionPane;

class MyThread_7 extends Thread {

	@Override
	public void run() {
		for (int i = 10; i > 0; i--) {
			System.out.println("\t\t" + i);

			try {
				sleep(1000); //extends Thread 했음으로 Thread.classd 안해도 됨
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

		}
	}
}

public class ThreadTest_7 {
	public static void main(String[] args) {

		MyThread_7 thread_1 = new MyThread_7();
		thread_1.start();

		String input = JOptionPane.showInputDialog("아무 값이나 입력 : ");
		System.out.println("입력한 값 : " + input + "입니다 ~~");

	}
}

 

 

--   스레드 우선 순위 지정    --

 

: 멀티 스레드는 여러개의 스레드가 동시에 실행도니다고 하였지만 이것은 일반적인 설명이고 실제로는 그렇지 않다.

대부분의 컴퓨터는 한개의 CPU만 가지고 있다.

그렇기 때문에 실제로 여러 스레드 중에서 하나의 스레드만 수행한다.

스레드가 작동하는 방식은 하나의 스레드가 종료할때까지 CPU를 독점하는 것이아니고 여러개의 스래드를 번갈아가며 처리한다. 이처럼 여러개의 스래드를 교차하면서 처리하는 것을 Scheduling(스케줄링) 이라고한다.

여러개의 스레드 중에서 어떤 스래드가 CPU를 사용할지는 스레드 스케줄러가 결정해 처리한다. 

따라서 자바 스레드의 정확한 작동을 예측하기는 힘들다. 

하지만 setPriority( ) 메소드를 이용해 우선 순위를 부여 할 수 있다.

 

자바는 총 10단계 우선순위를 갖는다. 그중에서 3가지 우선순위는 Thread 클래스의 맴버상수(불변의 수)로 정의되어 있다.

(우선순위가 높으면 대체로 먼저 수행되지만 항상 그런것은 아니다.)

디폴트 값은 5 이다.  숫자가 높을수록 먼저 실행됨. 

static final int   MAX_PRIORITY 스레드가 가지는 최고 우선 순위 값  (1)
static final int   MIN_PRIORITY 스레드가 가지는 최소 우선 순위 값   (1)
static final int   NORM_PRIORITY 스레드가 가지는 보통 우선 순위 값    (10)

 

obj.setPriority (숫자 혹은 MAX_PRIORITY 등 )  //obj의 우선순위 지정
obj.getPriority( )   // obj의 우선순위를 반환함.   

 

(예시) - 우선순위

class Thread_8 extends Thread {
	public Thread_8(String name) {
		super(name);
	}

	public void run() {
		for (int i = 0; i < 20; i++) {
			System.out.println(getName() + "스레드");
		}
	}

}

public class ThreadTest_8 {
	public static void main(String[] args) {
		// 우선순위가 높으면 대체로 먼저 수행되지만 항상 그런것은 아니다.
		Thread_8 obj_1 = new Thread_8("10 번"); 
		Thread_8 obj_2 = new Thread_8("     5 번");
		Thread_8 obj_3 = new Thread_8("          1 번");
		Thread_8 obj_4 = new Thread_8("       7번");

		obj_1.setPriority(7); 
		obj_2.setPriority(Thread.NORM_PRIORITY);
		obj_3.setPriority(Thread.MIN_PRIORITY);// new Thread_8(1)이라고 적어도 된다.
		
		System.out.println("a : "+obj_1.getPriority()); // 우선순위 번호 보여줌
		System.out.println("b : "+obj_2.getPriority());
		System.out.println("c : "+obj_3.getPriority());
		System.out.println("d : "+obj_4.getPriority());

		obj_3.start();
		obj_2.start();
		obj_1.start();

	}

}

 

 

(예시) - 우선순위

public class ThreadTest_9 {
	public static void main(String[] args) {

		Thread_9_1 obj_1 = new Thread_9_1();
		Thread_9_2 obj_2 = new Thread_9_2();

		obj_1.setPriority(8);
		obj_2.setPriority(2);

		obj_1.start();
		obj_2.start();

	}

}

class Thread_9_1 extends Thread {

	@Override
	public void run() {
		for (int i = 0; i < 500; i++) {
			System.out.print("--");
			for (int j = 0; j < 10000000; j++)
				;

		}
	}
}

class Thread_9_2 extends Thread {
	@Override
	public void run() {

		for (int i = 0; i < 500; i++) {
			System.out.print("|");

			for (int j = 0; j < 1000000; j++)
				;

		}

	}
}

 

 

 

 

obj . join() ;        : 해당스레드(obj)가 종료될때까지 기다리고 다음 스레드로 넘어간다.

                          예외처리가 필요하다. 

(예시) join

public class ThreadTest_10 {
	public static void main(String[] args) {

		Thread_10_1 obj_1 = new Thread_10_1();
		Thread_10_2 obj_2 = new Thread_10_2();
		Thread_10_3 obj_3 = new Thread_10_3();

		obj_1.start();
		try {
			obj_1.join();
			// jion을 사용하면 해당 스레드가 종료될떄까지 기다리고 다음 스레드로 넘어간다.
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		obj_2.start();
		obj_3.start();

	}

}

class Thread_10_1 extends Thread {

	@Override
	public void run() {
		for (int i = 0; i < 300; i++) {
			System.out.print("-");

		}
	}
}

class Thread_10_2 extends Thread {

	@Override
	public void run() {
		for (int i = 0; i < 300; i++) {
			System.out.print("|");

		}
	}
}

class Thread_10_3 extends Thread {

	@Override
	public void run() {
		for (int i = 0; i < 300; i++) {
			System.out.print("#");

		}
	}
}

 

 


 

 

Runnable 인터페이스 사용 (Tread 클래스의 추상메소드인 run() 메소드를 재정의)

 

이미 다른클래스를 상속받아 Thread 클래스를 상속받아 사용할수 없을때 Runnable 인터페이스를 이용한다.

 

Runnable  사용방법

 

1.Runnable 인터페이스를 implements 하는 클래스를 정의한다.

 

2.Runnable 인터페이스의 run( )  매소드를 재정의한다.(Runnable 안에는 run()메소드만 존재 , 반드시 재정의 해야함)

class RunnableTest implements Runnable{ //Runnable 인터페이스 구현

   public void run() {  // run()메소드 재정의
      ......... ;
   }

}

3. main( ) 메소드에서 Runnable 인터페이스를 구현한 클래스 객체를 생성

 

4. Thread 클래스 객체를 생성하면서 생성자에 3.에서 생성한 객체를 매개변수로 전달

 

5. 4.에서 생성한 객체를 사용해 start( )메소드 호출

public static void main(String[] args){

   RunnableTest obj = new RunnableTest(); // 객체 생성
   Thread thread = new Thread(obj); // Thread 클래스 객체 생성
   
   Thread.start(); // start()메소드 호출
  }

 

 

(예시)  Runnable 사용방법

class Runnable_1 implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(" " + i + "번" + i + "*" + i + "=" + (i * i));
		}
	}

}

public class RunnableTest_1 {
	public static void main(String[] args) {

		Runnable_1 obj = new Runnable_1();
		Thread thread = new Thread(obj);

		thread.start();

	}

}

 

★(예시)  Thread 방법 / Runnable 방법을 이용하여 같은 문구 출력하기

1. thread 방법

class MyThread_3 extends Thread {

	public MyThread_3(String name) {
		super(name); //전달된 것이 Tread의 getName() 메소드로 넘어감 
	}

	@Override
	public void run() {
		try {
			for (int i = 0; i < 5; i++) {
				Thread.sleep(1000);
				System.out.println(" " + i + " 번" + getName() + ": 스레드");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();

		}
	}

}

public class ThreadTest_3 {
	public static void main(String[] args) {

		MyThread_3 obj_1 = new MyThread_3("첫번째");
		MyThread_3 obj_2 = new MyThread_3("두두번째");
		MyThread_3 obj_3 = new MyThread_3("세세세번째");

		obj_1.start(); // 스레드 호출 -뒤죽박죽나옴 obj_123이 동시에 돌아감
		obj_2.start();
		obj_3.start();
		

	}

}

 

 

2.Runnable 방법

class RunnableT implements Runnable {

	String name;
//public String getName(){   return name ; } : getname을 만드는 방법
	RunnableT(String name) {
		this.name = name;

	}

	@Override
	public void run() {

		try {
			for (int i = 0; i < 5; i++) {
				Thread.sleep(1000);
				System.out.println(" " + i + " 번" + name + ": 스레드");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();

		}

	}
}

public class Runable_1 {
	public static void main(String[] args) {

		RunnableT obj1 = new RunnableT("첫번째");
		RunnableT obj2 = new RunnableT("두두번째");
		RunnableT obj3 = new RunnableT("세세세번째");

		Thread thread1 = new Thread(obj1);
		Thread thread2 = new Thread(obj2);
		Thread thread3 = new Thread(obj3);

		thread1.start();
		thread2.start();
		thread3.start();

	}

}

RunnableT는 extends Thread가 된 1 번과 다르게 getName()을 사용할수 없음으로 다른방법을 생각해 주었다.

getName() 은 Thread 클래스의 것이다.

RunnableT의 생성자에서 name을 받아주는 방법으로 해결하였다.(This.name = name 이용)

 

또다른 방법으로는 

public String getName(){   return name ; }을  추가해 주어서 getName()이라는 메소드를 사용할수도 있다. 

생성자는 그대로 두어 name을 받아준다. 

 

 

 

(예제) - Runnable 방법에서 스레드에 우선순위를 지정하는 방법

class Run8 implements Runnable {
	String name;

	public Run8(String name) {
		this.name = name;
	}

	public void run() {
		for (int i = 0; i < 20; i++) {
			System.out.println(name + "스레드");
		}

	}
}

public class Rtest8 {
	public static void main(String[] args) {

		Run8 obj_1 = new Run8("10 번");
		Run8 obj_2 = new Run8("     5 번");
		Run8 obj_3 = new Run8("          1 번");
		Run8 obj_4 = new Run8("       7번");

		Thread th1 = new Thread(obj_1);
		Thread th2 = new Thread(obj_2);
		Thread th3 = new Thread(obj_3);
		Thread th4 = new Thread(obj_4);

		th1.setPriority(7);
		th2.setPriority(Thread.NORM_PRIORITY);
		th3.setPriority(Thread.MIN_PRIORITY);// new Thread_8(1)이라고 적어도 된다.

		System.out.println("a : " + th1.getPriority()); // 우선순위 번호 보여줌
		System.out.println("b : " + th2.getPriority());
		System.out.println("c : " + th3.getPriority());
		System.out.println("d : " + th4.getPriority());

		th1.start();
		th2.start();
		th3.start();
		th4.start();

	}
}

thread방법에서는  예를들어 supA extends Thread  이므로 suA가 Thread를 상속받아서 Thread의 것을 쓸수있지만 

Runnable은 Runnable a = new Runnable(); 을 해서 

이 a를 넣은 Thread  즉  Thread th = new Thread(a); 라고 하면 이 th.(Thread 안의 메소드명) 을 해서 

Thread 안의 메소드를 호출하여 사용 할 수 있다. 

 

 

 

 

 

 

(예제)

 

public class RunnerbleTest_3 implements Runnable {

	int delay;
	String title;

	public RunnerbleTest_3(String title) {
		this.title = title;
		System.out.println(" " + title + "쓰레드 시작 ~");
	} // 객체가 만들어지고 나서 스레드가 도는것이다. 즉 객체가 만들어지면서 이부분이 먼저 만들어지고 스레드가 돌게된다. 
	  // 객체가 만들어지는것이므로 이것은 스레드가 아님. 순서대로 만들어진다. 객체가 만들어지고 그다음에 start()가 됬을때 스레드가 돈다.

	public static void main(String[] args) {

		Thread obj_1 = new Thread(new RunnerbleTest_3("첫번째"));
		Thread obj_2 = new Thread(new RunnerbleTest_3("두두번째"));
		Thread obj_3 = new Thread(new RunnerbleTest_3("세세세번째"));

		obj_1.setPriority(Thread.MAX_PRIORITY);
		obj_1.setPriority(Thread.MIN_PRIORITY);
		obj_1.setPriority(7);

		obj_1.start();
		obj_2.start();
		obj_3.start();

	}

	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println("[" + i + "]" + title + "실행중$$$$");

		}
		System.out.println(title + "실행종료 ^^^^");
	}

}