Thread 클래스와 Runnable 인터페이스
프로세스
운영체제로부터 자원을 할당받은 작업의 단위
쓰레드
프로세스가 할당받은 자원을 이용하는 실행 흐름의 단위
자바에서 Thread를 만드는 방법
- Thread 클래스를 상속
- Runnable인터페이스를 구현
쓰레드를 상속 받는 방법
java.lang.Thread클래스를 상속받는다.
Thread가 가지고 있는 run()메소드를 오버라이딩
class MyThread extends Thread {
String name;
public MyThread(String name){
this.name = name;
}
public void run(){
for(int i = 0; i < 10; i ++){
System.out.println("thread : " + this.name);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("쓰레드1");
MyThread myThread2 = new MyThread("쓰레드2");
myThread1.start();
myThread2.start();
}
}
생성한 쓰레드의 run 메서드을 호출하지 않는 이유
- 쓰레드는 자신만의 메모리 공간을 받아서 별도의 실행 흐름을 생성하는데
- run 메서드를 호출하는 것은 단순 메서드 호출일뿐 쓰레드 생성이 아니다.
- 메모리 공간 할당 등 쓰레드 실행을 위한 기반을 만들고 run 메서드 대신 호출 한다.
CPU가 하나여도 쓰레드는 생성가능하다.
쓰레드는 CPU를 공유하기 때문이다
메인 메서드가 종료되면 쓰레드는 종료?
메인 메서드가 종료되도 실행 중에 있는 쓰레드가 있으면 프로그램은 종료되지 않는다.
주요 메서드
start() | 쓰레드 시작 |
getState() | 쓰레드 상태를 돌려줌 |
getName() | 쓰레드 이름을 돌려줌 |
getPriority() | 쓰레드 우선순위를 돌려줌 |
sleep() | 쓰레드를 특정시간만큼 정지시킴 |
join() | 현재 쓰레드를 호출한 쓰래드가 종료될때까지 중지시킴 |
isAlive() | 쓰레드가 살아있는지 확인 |
Runnable를 구현하는 방법
쓰레드 클래스 정의를 위해서는 Thread 클래스 상속해야한다.
쓰레드 외에도 다른 상속이 필요하다면, 자바는 단일 상속만을 지원하기 때문에 문제가 생길 수 있다
class MyThread implements Runnable {
String str;
public MyThread(String str){
this.str = str;
}
public void run(){
for(int i = 0; i < 10; i ++){
System.out.println(str);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("쓰레드1");
MyThread myThread2 = new MyThread("쓰레드2");
Thread thread1 = new Thread(myThread1);
Thread thread2 = new Thread(myThread2);
thread1.start();
thread2.start();
}
}
MyThread는 Thread를 상속받지 않았기 때문에 Thread가 아니다.
Thread를 생성하고, 해당 생성자에 MyThread를 넣어서 Thread를 생성한다.
Thread 클래스가 가진 start()메소드를 호출
쓰레드의 상태
NEW
쓰레드 클래스가 new를 통해 인스턴스화 된 상태
JVM에서 관리 되는 상태는 상태가 아님
자바에서는 이 상태를 쓰레드로 표현
Runnable
쓰레드 인스턴스를 대상으로 start 메서드가 호출되면, 해당 쓰레드는 Runnable상태가 된다.
모든 실행의 준비를 마치고, 스케줄러에 의해 선택되어 실행되기만 기다리는 상태
Blocked
실행 중인 쓰레드가 sleep, join 을 호출하거나 CPU 할당이 필요하지 않는 입출력 연산을
하게 되면 CPU를 다른 쓰레드에게 양보하고 Block 상태가 된다.
다시 스케줄러의 선택 받아서 실행 되려면 Blocked 상태가 해결되어 Runnable상태가 되어야함.
Dead
run 메서드가 실행 완료되어 run 메서드를 빠져 나오면 해당 thread가 'Dead'가 된다
실행을 위해서 할당 받았던 메모리를 비롯해 각종 쓰레드 관련 정보가 완전히 사라짐
한번 사라진 Thread는 다시 Runnable 상태가 되지 못함
쓰레드의 우선순위
쓰레드 클래스의 setPriority메서드를 통해 쓰레드의 우선 순위를 정할 수 있다.
class MyThread extends Thread {
String name;
public MyThread(String name, int priority){
this.name = name;
setPriority(priority);
}
public void run(){
for(int i = 0; i < 100; i ++){
System.out.println("thread : " + this.name);
}
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("쓰레드1", Thread.MAX_PRIORITY);
MyThread myThread2 = new MyThread("쓰레드2", Thread.NORM_PRIORITY);
MyThread myThread3 = new MyThread("쓰레드3", Thread.MIN_PRIORITY);
myThread1.start();
myThread2.start();
myThread3.start();
}
}
-----------------------------------------------------
> Task :Main.main()
thread : 쓰레드1
thread : 쓰레드1
thread : 쓰레드3
thread : 쓰레드3
thread : 쓰레드2
쓰레드는 JVM에 동작하는 운영체제에 의해 실행 결과가 의존적이다. 예를 들어 10단계 쓰레드 개발을 해도 운영 체제에 영향을 받는다. 따라서 우선순위 변경시에는 상수로 정의되어있는 값을 선택해 변경하는 것이 운영체제에 따른 차이를 최소화 할 수 있다.
메인Thread
쓰레드의 가장 큰 역할은 별도의 실행흐로 형성이다. 별도의 실행흐름은 메서드 호출을 통해 형성된다.
run 메서드가 호출되고, run 메서드 내에서 다른 메서드를 호출해 main 메서드와는 다른 흐름 형성.
동기화
Thread의 순서를 정의하고, Thread가 순서에 따르는 것이 동기화이다.
자바는 synchronized 키워드를 사용해서 동기화 메소드를 선언하거나 동기화 블록을 지정하면 된다.
public synchronized void run() {
System.out.println();
}
메서드 선언에 synchronized를 선언하면 동기화 메서드가 된다. 동기화 메소드는 한 순간에 하나의 쓰레드만
호출 가능하다.
자바의 모든 인스턴스는 lock과 monitor 이라 불리는 열쇠가 존재한다.
synchornized 선언된 메소드를 호출하려면 먼저 열쇠를 획득해야한다. 메서드를 빠져나오면 열쇠는 반납된다.
동기화 블록
동기화를 모든 메서드 전부가 아닌 코드 일부로 제한 할 수도 있다.
public void run() {
System.out.println("===1===");
synchronized (this)
{
System.out.println("nested");
}
}
}
notify()
하나의 쓰레드를 깨운다
notifyAll
모든 쓰레드를 기다림
데드락
두 개이상의 thread가 서로 실행종료을 기다리면서 교착상태에 빠지는 상황
public class Main {
public static void main(String[] args) {
final Object lock1 = new Object();
final Object lock2 = new Object();
Thread thread1 = new Thread() {
public void run() {
synchronized (lock1) {
System.out.println("Thread 1 hold lock 1");
try {
Thread.sleep(100);
} catch (Exception e) { e.printStackTrace();}
synchronized (lock2) {
System.out.println("Thread 1 holds lock 2");
}
}
}
};
Thread thread2 = new Thread() {
public void run() {
synchronized (lock2) {
System.out.println("Thread 2 hold lock 2");
try {
Thread.sleep(100);
} catch (Exception e) { e.printStackTrace();
}
synchronized (lock1) {
System.out.println("Thread 2 holds lock 2");
}
}
}
};
thread1.start();
thread2.start();
}
}
Thread1에서는 lock1 이 동기화 걸린 상황에서 lock2을 동기화하고 있다
Thread2에서는 lock2 이 동기화 걸린 상황에서 lock1을 동기화하고 있다
데드락을 피하기 위한 방법
nested lock을 피한다
lock 순서를 준다
lock이 한개 이상 걸리는 상황을 피한다.
출처 : 난 정말 JAVA를 공부한 적이 없다구요