concurrency : 소프트웨어, 프로그래밍 상에 병렬성
parallelism : 하드웨어 , 물리적인 병렬성
멀티 태스킹의 주체는 프로세스이다. 프로세스는 실행중인 프로그램이다.
Process는 Thread들의 집합이다.
프로그램 3개가 동작하고 있다면
JAVA는 멀티 Thread 기반. JVM은 최소한 2가지 흐름으로 동작하는데
JVM은 OS에게 할당받은 시간을 자기 내부(t1, t2, t3 )에 존재하는 쓰레드에게 자원을 스케줄링 한다.
예측은 못하지만 만들어진 결과를 보고 어떻게 움직였는지 유추는 할 수 있어야한다.
public class ExtendThread extends Thread {
public void run() {
System.out.println("Thread 클래스를 상속");
}
}
public class ExtendThreadTest {
public static void main(String[] args) {
ExtendThread et = new ExtendThread();
// start()를 이용하여 스레드를 시작 시킨다.
// 이후 ExtendThread의 run()가 실행되고 run()가 종료되면 바로
// ExtendThread가 소멸된다.
et.start();
System.out.println("end of main");
}
}
Thread.run()
Thread가 할 일 정의
Thread.start()
Thread를 시작 시킨다. 이후 run() 실행되고 run() 종료되면 바로 ExtendThread가 소멸된다.
Thread 대기열에 main과 et가 대기하고 있다가 호출이 되면 실행된다.

Thread Lifecycle
New : Thread 객체는 생성되었지만, 아직 시작되지 않는 상태
Runnable : 실행되지 않는 상태, 대기열, runnable 상태
Run : 실행한 상태. 다시 대기열로 돌아왔다가 가상머신에 의해 선택되면 실행 상태가 된다.
Dead : Run 상태가 끝까지 다 수행하면 소멸된다. 한번 죽었던 Thread는 재활용이 안된다.
Not Runnable : 실행 불가능한 상태.
BLOCKED : Thread가 실행 중지 상태이며 사용자 입력을 기다리는 상태
TIME_WAITING : 특정 시간만큼 대기중인 상태
WAITING : 대기중인 상태
화살표의 방향을 잘 확인하자
public class RunnableThread implements Runnable {
// run을 오버라이딩하여 재정의.
@Override
public void run() {
System.out.println("Runnable 인터페이스를 구현");
}
}
public class RunnableThreadTest {
public static void main(String[] args) {
// Thread 생성자에 RunnableThread 인스턴스를 파라미터로 전달.
// t가 해야할 일은 전달받은 인스턴스이다.
Thread t = new Thread(new RunnableThread());
t.start();
System.out.println("end of main");
// 이너 클래스 활용
Thread t1 = new Thread() {
@Override
public void run() {}
};
}
}
Runnable 인터페이스를 구현해서 Thread를 실행하는 방법.
Java는 단일 상속을 원칙으로 하기 때문에 상속 받지 못한 경우 인터페이스로 구현한다.
public class ThreadEx1_1 extends Thread{
public void run() {
for(int i=0; i < 5; i++) {
System.out.println("t1 : " + getName());
}
}
}
public class ThreadEx1_2 implements Runnable {
@Override
public void run() {
// t == t2
Thread t = Thread.currentThread();
for(int i=0; i < 5; i++) {
// currentThread() -> 현재 구문을 수행하고 있는 객체
System.out.println("t2 : " + t.getName());
}
}
}
public class CreateThread {
public static void main(String[] args) {
ThreadEx1_1 t1 = new ThreadEx1_1();
Runnable r = new ThreadEx1_2();
Thread t2 = new Thread(r);
t1.start();
t2.start();
System.out.println(Thread.currentThread().getName());
}
}
상속 받은 Thread 객체와 구현된 Runnable을 실행하는 코드.
public class NormalProcess {
public static void main(String[] args) {
// 사용자 입력이 있을때까지 BLOCKED 상태가 된다. -> Not Runnable 상태
// 사용자 입력이 되면 Runnable 대기 상태로 돌아갔다가 Run 상태가 된다.
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요");
System.out.println("입력하신 값은 " + input + "입니다.");
for(int i=10; i > 0; i--) {
System.out.println(i);
try {
// sleep : TIMED_WAITING 상태. 실행 불가 상태(Not Runnable)에서 대기하는 시간.
// Run -> Not Runnable -> Runnable -> run -> Not Runnable... 반복한다.
Thread.sleep(1000);
} catch(Exception e) {
}
}
}
}
싱글 Thread를 표현했다. 순차적으로 스케줄링이 되기 때문에 사용자 입력을 받고 나면 for문이 실행되어 출력된다.
public class ThreadEx extends Thread{
public void run() {
for (int i = 10; i > 0; i--) {
System.out.println(i);
try {
sleep(1000);
} catch (Exception e) {}
}
}
}
public class UsingThread {
public static void main(String[] args) throws Exception {
/*
* 프로그램이 언제 종료되는지 정확하게 알 수 있다.
* 콜 스택이 비워졌다 = 프로그램이 할 일이 끝났다.
* 쓰레드 마다 자신만의 콜 스택을 가진다.
* java 프로그램이 종료 = 모든 콜 스택이 비워졌다.
-> main 쓰레드가 끝나도 다른 쓰레드가 살아있다면 프로그램은 끝나지 않는다.
*
*/
ThreadEx th1 = new ThreadEx();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + "입니다.");
}
}
public class NormalProcess0 {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 300; i++) {
System.out.print("-");
}
System.out.println(
"소요시간1 : " +
(System.currentTimeMillis() - UsingThreadProcess.startTime)
);
for (int i = 0; i < 300; i++) {
System.out.print("|");
}
System.out.println(
"소요시간2 : " +
(System.currentTimeMillis() - UsingThreadProcess.startTime)
);
}
}
싱글 Thread로 구현된 코드.
class ThreadEx0 extends Thread {
public void run() {
for (int i = 0; i < 300; i++) {
System.out.print("|");
}
System.out.println(
"소요시간2 : " +
(System.currentTimeMillis() - UsingThreadProcess.startTime)
);
}
}
public class UsingThreadProcess {
static long startTime = 0;
public static void main(String[] args) {
ThreadEx0 th1 = new ThreadEx0();
startTime = System.currentTimeMillis();
th1.start();
for (int i = 0; i < 300; i++) {
System.out.print("-");
}
System.out.println(
"소요시간1 : " +
(System.currentTimeMillis() - UsingThreadProcess.startTime)
);
}
}
싱글 쓰레드와 멀티 쓰레드의 소요시간 차이 확인 할 수 있다.
단일 쓰레드로 동작 시키는게 빨리 끝난다.
왜?
멀티 쓰레드 수행 시 t1과 t2에 1 ~ 4까지 있다고 하면 t1 - 1을 수행하고 나서 어디까지 진행하고 났는지 정보를 저장하고 t2 -1이 수행하고 진행한 정보를 저장하고 t1 - 2를 수행하는데 1이 어디까지 했는지 정보를 불러와서 2를 수행하고 나서 어디까지 진행하고 났는지 정보를 저장하고 t2 - 2를 수행한다. 이 작업들을 반복하는 Context Switching(문맥교환)이 일어난다.
응답성이나 반응성을 높이고 싶으면 멀티 쓰레드를 사용한다.
run() 메서드와 main은 역할이 같다.
Thread를 구현하는 방법 2가지
쓰레드는 왜 쓰는가?
동시에 여러가지 해야하는 일들이 존재할때.
쓰레드는 사용하면 반응성이 올라간다.
public class NormalThreadTest {
public static void main(String[] args) {
//스레드 생성
Thread t = new Thread() {
public void run() {
try {
Thread.sleep(5000);
System.out.println("MyThread 종료");
} catch(Exception e) {}
}
};
// 스레드 시작
t.start();
// main 메서드 종료 메세지
System.out.println("main() 종료");
}
}
public class DaemonThreadTest {
public static void main(String[] args) {
// 스레드 생성
Thread t = new Thread(){
public void run() {
try {
// 5초간 멈춤
Thread.sleep(5000);
// 스레드 종료 메세지
System.out.println("MyThread 종료");
} catch (Exception e) {
}
}
};
// 반드시 start() 호출 전에 사용해야 한다.
// 데몬 스레드로 설정
t.setDaemon(true);
// 스레드 시작
t.start();
// main 메서드 종료 메세지
System.out.println("main() 종료");
}
}
모든 스레드의 콜스택이 비워졌을때 프로그램이 끝난다. main 에서 프로그램이 종료 되더라도 나머지 스레드가 작동 중이라면 프로그램은 끝나지 않는다.
Daemon Thread
반드시 start() 호출 전에 사용해야 한다.
일반 Thread들이 종료되면 Daemon Thread도 따라 종료된다.
사용자가 종료를 명령 했을때 종료가 지연되는걸 방지한다.
class SomeThread extends Thread {
// 생성자에 문자열 넣어주면 이름이 된다.
public SomeThread(String name) {
super(name);
}
@Override
public void run() {
String name = this.getName();
for (int i = 0; i < 10; i++) {
System.out.println(name + " is working");
try {
Thread.sleep(500);
} catch (Exception e) { }
}
}
}
public class RunningTest {
public static void main(String[] args) {
SomeThread t1 = new SomeThread("A");
SomeThread t2 = new SomeThread("B");
SomeThread t3 = new SomeThread("C");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.NORM_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
System.out.println(t3.getPriority());
t1.start();
t2.start();
t3.start();
}
}
setPriority(int newPriority) / getPriority() 메서드를 통해 쓰레드의 우선순위를 반환하거나 변경할 수 있다.
main Thread는 우선순위가 NORM_PRIORITY(5)이다.
MIN_PRIORITY : 1
NORM_PRIORITY : 5
MAX_PRIORITY : 10
Thread의 우선순위가 높다고 해서 먼저 실행되는게 아니라 확률상으로 높아진다. 영향을 줄 수 있지만 절대적이지 않다. 절대적인 권한을 가지고 있다면 우선순위를 부여해서 활용을 할 수 있지만 보통은 건드리지 않는게 좋다.