기존 중요 부분 : 자료구조, 알고리즘 - 이제 검색하면 다 나옴
현재 : 운영체제, 소프트웨어공학 <- 중요
쓰레드 <- 이해하기 직접 쓰진 않을 것
컴퓨터에서 프로세스란 '현재 돌아가고 있는 프로그램'을 말한다.
- 병렬진행 : 하드웨어적 요소 -> 한 번에 여러 개의 프로세스를 진행 (ex) 듀얼코어)
- 병행진행 : 소프트웨어적 요소 -> 프로세스를 빠르게 번갈아가며 진행
배수율과 클럭이 속도를 관장하지 (c.f. 오버클럭) 코어 개수는 속도보다는 성능에 관계됨
프로세스 내부에 존재하는 '(병행 진행되는) 작업'을 의미한다.
진행중인 여러 프로그램(=프로세스)의 동시진행이 가능 - OS가 자원 할당
프로그램 내에서 여러 작업(=쓰레드)이 동시진행이 가능 - 가상머신이 자원 할당
프로세스와 마찬가지로 쓰레드에 자원 배분을 우리가 결정할 수 없으나,
우리는 쓰레드를 이해하고 시나리오를 예측하고 결과를 제어할 수 있어야 한다.
💦 쓰레드 실행 순서 예측의 어려움
thread-0, thread-1, thread-2... 와 같이 복수의 쓰레드가 존재할 때,
- 다음에 실행될 thread가 무엇일지 알 수 없다. (연속으로 중복되게 뽑힐 수 있음)
- 실행된 thread가 얼마나 지속될지 알 수 없다. (한 번만에 바뀌기도, 오래 지속되기도?)
//Thread 클래스를 상속
class ExtendThread extends Thread {
public void run() {
// run()을 오버라이딩하여 재정의
System.out.println("Thread 클래스를 상속 : " + Thread.currentThread().getName());
}
}
public class ExtendThreadTest {
public static void main(String[] args) { // main 또한 thread (main thread)
ExtendThread et = new ExtendThread();
// start()를 이용하여 스레드를 시작시킨다.
// 이후 ExtendThread의 run()이 실행되고, run()이 조욯되면 바로 ExtendThread가 소멸된다.
et.start();
// et.start() <- start()의 소유자 et, start()를 실행시키는 주체 thread
// start()를 통해 새로운 쓰레드를 시작시킨다. (이때부터 병행진행)
System.out.println("end of main : " + Thread.currentThread().getName());
}
}
따로 이름을 붙이지 않으면 thread-0, thread-1, ... 순으로 쓰레드의 이름이 붙는다.
- 쓰레드 진행의 동시성 참고 코드
class MyThread extends Thread { @Override public void run() { String name = Thread.currentThread().getName(); for(int i = 0; i < 10000; i++) { System.out.println(name); } } } public class ThreadEx1 { public static void main(String[] args) { // main 또한 thread (main thread) MyThread t = new MyThread(); t.start(); String name = Thread.currentThread().getName(); for(int i = 0; i < 10000; i++) { System.out.println(name); } } }
- 결과 :
- main과 thread-0이 번갈아 나옴.
- 공평하게 나누어가지는 것을 의미하지 x, 누가 먼저 나올지 보장하지 않음.
// Runnable 인터페이스를 구현
class RunnableThread implements Runnable {
// run()을 오버라이딩하여 재정의
public void run() {
System.out.println("Runnable 인터페이스를 구현");
}
}
public class RunnableThreadTest {
public static void main(String[] args) {
// r은 쓰레드 아님. 쓰레드가 할 일을 나타내주는 객체.
RunnableThread r = new RunnableThread();
// Thread 생성자에 RunnableThread 인스턴스를 파라미터로 전달
Thread t = new Thread(r);
t.start();
System.out.println("end of main");
}
}
Thread에 직접 작성 vs Thread에 Runnable 인터페이스 객체 전달
- 어느 방법이 낫다고 말하기 어렵지만, 후자는 객체 상속 여지를 남겨둔다는 장점이 있다.
class ThreadEx2 extends Thread {
public void run() {
for(int i = 0; i < 300; i++) {
System.out.println("|");
}
System.out.println(
"소요시간2 : " +
(System.currentTimeMillis() - UsingThreadProcess.startTime)
);
}
}
public class UsingThreadProcess {
static long startTime = 0;
public static void main(String[] args) {
ThreadEx2 th1 = new ThreadEx2();
startTime = System.currentTimeMillis();
th1.start();
for(int i = 0; i < 300; i++) {
System.out.print("-");
}
System.out.println(
"소요시간1 : " +
(System.currentTimeMillis() - UsingThreadProcess.startTime)
);
}
}
스윙에는 EventDispacherThread가 존재. 예외처리를 할 때도 프로그램 돌아감.
스윙 배우기 전에 정말 main만 다룰 때는 예외처리로 넘어가면 프로그램 종료됨.
참고 - [자바] Event-Dispatching Thread
참고 - Java Event Dispatch Thread
Object에도 쓰레드 관련 메서드 존재 -> 자바는 쓰레드를 사용하는 언어다.
public class NormalThreadTest {
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) {
// 무시...
}
}
};
// 스레드 시작
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() 종료");
}
}
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(InterruptedException 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);
t1.start();
t2.start();
t3.start();
}
}
main thread의 우선순위는 NORM_PRIORITY(5) 이다.
start()
run()
join()
wait()
notify()
, notifyAll()
interrupt()
join()
: 호출하면 호출한 쓰레드가 실행불가 상태로 전환.public class ThreadJoinTestB {
public static void main(String[] args) {
// 스레드 생성
Thread t = new Thread() {
public void run() {
try {
// 2초간 멈춤
Thread.sleep(2000);
// 스레드 종료 메시지
System.out.println("MyThread 종료");
// 3초간 멈춤
Thread.sleep(3000);
} catch (Exception e) {}
}
};
// 스레드 시작
t.start();
try {
// join 메서드 실행...
// t 스레드가 종료될 때까지 main 스레드가 기다림. (실행불가 상태로 전환)
t.join();
// t가 영향을 받는 것이 아니라, t를 실행한 쓰레드가 영향을 받는 경우!!!
// t를 호출한 쓰레드가 실행불가 상태로 전환됨.
// 이처럼 메서드 소유 객체가 아니라 메서드 호출 객체가 영향을 받는 케이스가 앞으로 종종 나올 것.
} catch(InterruptedException e) {
// InterruptedException :
e.printStackTrace();
}
// main 종료 메시지
System.out.println("main() 종료");
}
}
class ThreadEx13_1 extends Thread {
public void run() {
for(int i = 0; i < 300; i++) {
System.out.print("-");
}
} // run()
}
class ThreadEx13_2 extends Thread {
public void run() {
for(int i = 0; i < 300; i++) {
System.out.print("|");
}
} // run()
}
public class ThreadEx13 {
static long startTime = 0;
public static void main(String[] args) {
ThreadEx13_1 th1 = new ThreadEx13_1();
ThreadEx13_2 th2 = new ThreadEx13_2();
th1.start();
th2.start();
startTime = System.currentTimeMillis();
try {
th1.join();
} catch(InterruptedException e) {}
try {
th2.join();
} catch (InterruptedException e) {}
System.out.print("소요시간 : " + (System.currentTimeMillis() - ThreadEx13.startTime));
} // main()
}
동기화를 보장하는 (thread-safe한) 객체들
Vector
,HashSet
,Hashtable
,StringBuffer
...
synchronized
: lock을 가진 객체에게만 내부 접근을 허용하는 키워드. method
: lock의 주체는 this(메서드 보유객체)block
: lock의 주체를 본인이 결정static method
: lock의 주체는 클래스 객체(메서드 보유한 클래스 객체)lock
을 가지고 태어난다. lock은 한 번에 하나의 객체만 빌려갈 수 있다. 대드락DeadLock
모든 쓰레드가 종료 혹은 실행불가 상태로 정적인 상태로 프로그램이 더이상 진행되지 않는 것. 쓰레드 상태 전이 없음.
(sleep처럼 기다리면 풀리는 실행불가x)
라이브락LiveLock
대드락과 달리 동적인 상태로 프로그램이 더이상 진행되지 않는 것. 쓰레드 상태 전이 존재.
(쓰레드가 동작하지만 프로그램의 목적에 다가가지 못함.)
기아 상태
기다려도 영원히 자원을 받지 못하는 상태.
public class SynchVsNotSynch {
private static final long CALL_COUNT = 100000000L;
public static void main(String[] args) {
trial(CALL_COUNT, new NotSynch());
trial(CALL_COUNT, new Synch());
}
private static void trial(long count, Object obj) {
String msg = obj.toString();
System.out.println(msg + " : BEGIN");
long startTime = System.currentTimeMillis();
for(int i = 0; i < count; i++) {
obj.toString();
}
System.out.println(
"Elapsed time = " +
(System.currentTimeMillis() - startTime) + "ms"
);
System.out.println(msg + " : END");
}
}
class Synch {
private final String name = "Synch";
@Override
public synchronized String toString() {
return "[" + name + "]";
}
}
class NotSynch extends Synch {
private final String name = "NotSynch";
@Override
public String toString() {
return "[" + name + "]";
}
}
class KeyA {}
class KeyB {}
class Human2 {
private String name;
private int age;
private int birthYear;
private KeyA keyA = new KeyA(); // lockA
private KeyB keyB = new KeyB(); // lockB
// keyA의 lock 범위
public void setName(String name) {
synchronized(keyA) {
try {
this.name = name;
System.out.println("name changed");
Thread.sleep(3000);
} catch(Exception e) {}
}
}
// keyB의 lock 범위
public void setAge(int age) {
synchronized(keyB) {
try {
this.age = age;
System.out.println("age changed");
Thread.sleep(3000);
} catch(Exception e) {}
}
}
public void setBirthYear(int birthYear) {
try {
this.birthYear = birthYear;
System.out.println("age changed");
Thread.sleep(3000);
} catch(Exception e) {}
}
// keyB lock 여기까지
}
public class SynchronizedBlockCase {
public static void main(String[] args) {
final Human2 h = new Human2();
Thread t1 = new Thread() {
@Override
public void run() {
h.setName("춘식");
}
};
t1.setName("first");
Thread t2 = new Thread() {
@Override
public void run() {
h.setAge(10);
}
};
t2.setName("second");
Thread t3 = new Thread() {
@Override
public void run() {
h.setBirthYear(2013);
}
};
t3.setName("third");
t1.start();
t2.start();
t3.start();
// t1과 t2는 관계없이 자유롭게 진행되지만,
// t2와 t3는 서로 진행에 제약을 받는다.
}
}
synchronized method
대비 synchronized block
의 장점wait()
: 객체의 락을 가진 상태로 synchronized 범위 안에 들어간 쓰레드에게, 락을 풀고 실행불가 상태로 전환할 것을 명령.nofity()
혹은 notifyAll()
을 통해 다시 실행(대기)상태로 바꾼다.
notify()
는 기아상태를 부를 수 있어 잘 사용하지 않는다.
class Producer2 extends Thread {
private MyBox2 box;
public Producer2(MyBox2 box) {
this.box = box;
}
@Override
public void run() {
for(int i = 0; i < 20; i++) {
box.put(i);
try {
sleep(100);
} catch(InterruptedException e) {}
}
}
}
class Consumer2 extends Thread {
private MyBox2 box;
public Consumer2(MyBox2 c) {
box = c;
}
@Override
public void run() {
int value = 0;
for(int i = 0; i < 10; i++) {
box.get();
try {
sleep(100);
} catch(InterruptedException e) {}
}
}
}
class MyBox2 {
private int contents;
private boolean isEmpty = true;
public synchronized int get() {
while(isEmpty) {
try {
wait(); // -> 실행불가열에 넣기
} catch(InterruptedException e) {}
}
isEmpty = !isEmpty;
notifyAll();
System.out.println(
Thread.currentThread().getName() + " : 소비 " + contents
);
return contents;
}
public synchronized void put(int value) {
while(!isEmpty) {
try {
wait();
} catch (InterruptedException e) {}
}
contents = value;
System.out.println(
Thread.currentThread().getName() + " : 생산 " + value
);
isEmpty = !isEmpty;
notifyAll();
}
}
public class ProducerConsumer2 {
public static void main(String[] args) {
MyBox2 c = new MyBox2();
Producer2 p1 = new Producer2(c);
Consumer2 c1 = new Consumer2(c);
Consumer2 c2 = new Consumer2(c);
p1.start();
c1.start();
c2.start();
}
}
Runnable
상태로 전환하는 메서드InterruptedException
을 발생시키고 예외처리문으로 넘어간다.interrupted = true
값을 가지며, 실행불가 상태를 탈출한 쓰레드는 초기화되어 interrupted = false
값을 가짐notifyAll()
이 더 안전한 방법.class Dummy {
public synchronized void todo() {
try {
System.out.println("start...");
wait();
} catch (InterruptedException e) {
System.out.println("interrupted!!!");
}
}
}
public class InterruptEx {
public static void main(String[] args) {
final Dummy d = new Dummy();
Thread t1 = new Thread() {
@Override
public void run() {
d.todo();
System.out.println("I'm dead");
}
};
t1.start();
t1.interrupt();
}
}
interrupt()
: 해당 쓰레드 객체에게 인터럽트를 건다. isInterrupted()
: 해당 쓰레드 객체가 인터럽트를 받았는지 확인한다.Thread.interrupted()
: 현재 쓰레드(static)가 인터럽트 받았는지 확인하고 상태를 초기화한다. public class InterruptEx2 {
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
long count = 0;
while(!isInterrupted()) {
// 뭔가 한다.
count++;
/*
try {
Thread.sleep(2000);
} catch(InterruptedException e) {
System.out.println("초기화");
}
*/
}
System.out.println("interrupted -> count = " + count);
System.out.println("isInterrupted : " + isInterrupted());
}
};
t1.start();
try {
Thread.sleep(1000);
} catch (Exception e) {}
t1.interrupt();
// interrupt 타이밍을 잡을 수 없는 경우
// while문 안에
// 1. 긴 명령이 들어간 경우 (조건을 확인하기 전까지 중단되지 않는다.)
// 2. wait, join, sleep로 인한 예외처리부가 존재하는 경우 (조건 확인 전에 interrupted 값이 초기화된다.)
// -> try ~ catch ~ 문을 while문 바깥에 두기 -> 하지만 어쨌든 예외 발생하고 중단되는 타이밍을 잘 인지해야 한다는 점.
}
}
root
: db 전체관리자 (최고관리자)
mysql -uroot -p
: 'mysql에 root라는 유저에 password를 통해 로그인하겠다'