프로세스 : 쓰레드 = 공장 : 일꾼
하나의 새로운 프로세스를 생성하는 것보다 하나의 새로운 쓰레드를 생성하는 것이 더 적은 비용이 든다.
run()
의 몸통{}
을 채우는 것이다.run()
의 몸통을 채우는 방식으로 2가지 방법이 있는데 Thread 클래스 상속과 Runnable 인터페이스 구현이 그것이다.class MyThread extends Thread {
public void run() { //Thread 클래스의 run()을 오버라이딩
/* 작업내용 */
}
}
// 쓰레드의 생성
MyThread t1 = new Thread();
t1.start();
class MyThread2 implements Runnable {
public void run() { // Runnable 인터페이스의 추상 메서드 run()을 구현
/* 작업 내용 */
}
}
// 쓰레드의 생성
Runnable r = new MyThread2();
Thread t2 = new Thread(r); // Thread(Runnable r)
// Thread t2 = new Thread(new MyThread2());
t2.satrt();
public class ThreadEx1 {
public static void main(String args[]) {
ThreadEx1_1 t1 = new ThreadEx1_1();
Runnable r = new ThreadEx1_2();
Thread t2 = new Thread(r); // 생성자 Thread(Runnable target)
t1.start();
t2.start();
}
}
class ThreadEx1_1 extends Thread {
public void run() {
for (int i = 0; i < 500; i++) {
// 조상인 Thread의 getName() 호출
System.out.print(0);
}
}
}
class ThreadEx1_2 implements Runnable {
public void run() {
for (int i = 0; i < 500; i++) {
// Thread.currentThread() - 현재 실행중 인 Thread를 반환한다.
System.out.print(1);
}
}
}
static Thread currentThread()
현재 실행중인 쓰레드의 참조를 반환한다.currentThread()
를 호출해 쓰레드에 대한 참조를 얻어와야만 호출이 가능하다.String getName()
쓰레드의 이름을 반환한다.start()
를 호출해야 쓰레드가 작업을 실행한다.start()
했다고 해서 즉시 실행되는것이 아니다.start()
했다고 해서 먼저 실행되는 것이 아니다.ThreadEx1_1 t1 = new ThreadEx1_1(); // 쓰레드가 t1을 생성한다.
ThreadEx1_1 t2 = new ThreadEx1_1(); // 쓰레드가 t2을 생성한다.
t1.start(); // 쓰레드 t1을 실행시킨다.
t2.start(); // 쓰레드 t2을 실행시킨다.
한 번 실행된 쓰레드는 다시 실행할 수 없다. 즉, 하나의 쓰레드에 대해 start()가 한 번만 호출될 수 있다. 만일 쓰레드의 작업을 한번 더 수행해야 한다면 새로운 쓰레드를 생성한 다음에 satrt()를 실행해야 한다.
ThreadEx1_1 t1 = new ThreadEx1_1(); t1.start(); t1.satrt(); // IllegarThreadStateException 발생
ThreadEx1_1 t1 = new ThreadEx1_1(); t1.start(); t1 = new ThreadEx1_1(); // 다시 생성 t1.start(); // OK
왜 쓰레드를 실행시킬때
run()
이 아닌start()
를 호출할까?
run()
: 생성된 쓰레드를 실행시키는 것이 아니라 단순히 클래스에 선언된 메서드를 호출하는 것이다.start()
: 새로운 쓰레드가 작업을 수행하는데 필요한 호출스택(call stack)을 생성한 다음 run()
을 호출해 생성된 호출스택에 run()
이 첫번째로 올라가게 한다.class ThreadTest {
public static void main(String args[]) {
MyThread t1 = new MyThread();
t1.start();
}
}
class MyThread extends Thread {
public void run() {
// ...
}
}
class ThreadTest {
public static void main(String args[]) {
for (int i = 0; i < 300; i++) {
System.out.println("-");
}
for (int i = 0; i < 300; i++) {
System.out.println("|");
}
} // main
}
작업 A와 B를 하나의 쓰레드가 실행한다.
class ThreadTest {
public static void main(String args[]) {
MyTread1 th1 = new MyThread1();
MyThread th2 = new MyThread2();
th1.start();
th2.start();
}
}
class MyThread1 extends Thread {
public void run() {
for (int i = 0; i < 300; i++) {
System.out.println("-");
}
} // run()
}
class MyThread2 extends Thread {
public void run() {
for (int i = 0; i < 300; i++) {
System.out.println("|");
}
} // run()
}
JVM 종류에 따라 쓰레드 스케줄러의 구현방법이 다를 수 있기 때문에 멀티쓰레드로 작성도니 프로그램을 다른 종류의 OS에서도 테스트해 볼 필요가 있다.
여러 쓰레드가 여러 작업을 동시에 진행하는 것을 병행(concurrent)라고 하고, 하나의 작업을 여러 쓰레드가 나눠서 처리하는 것을 병렬(parallel)이라고 한다.
public class ThreadEx6 {
public static void main(String[] args) throws Exception {
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하시신 값은 " + input + "입니다.");
for (int i = 10; i > 0; i--) {
System.out.println(i);
try {
Thread.sleep(1000); // 1초간 시간을 지연한다.
} catch(Exception e) {}
}
}
}
package _ch12;
import javax.swing.JOptionPane;
public class ThreadEx7 {
public static void main(String[] args) {
ThreadEx7_1 th1 = new ThreadEx7_1();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + " 입니다.");
}
}
class ThreadEx7_1 extends Thread {
public void run() {
for (int i = 10; i > 0; i--) {
System.out.println(i);
try {
sleep(1000);
} catch(Exception e) {}
}
} // run()
}
void setPriority(int newPriority) 쓰레드의 우선순위를 지정한 값으로 변경한다.
int getPriority() 쓰레드의 우선순위를 반환한다.
pubilc static final int MAX_PRIORITY = 10 // 최대 우선 순위
pubilc static final int MIN_PRIORITY = 1 // 최소 우선순위
pubilc static final int NORM_PRIORITY = 5 // 보통 우선순위
main()
를 수행하는 쓰레드는 우선순위가 5이므로 main 메서드 내에서 생성하는 쓰레드의 우선 순위는 자동으로 5가 된다.package _ch12;
public class ThreadEx8 {
public static void main(String[] args) {
ThreadEx8_1 th1 = new ThreadEx8_1();
ThreadEx8_2 th2 = new ThreadEx8_2();
th2.setPriority(7);
System.out.println("Priority of th1(-) : " + th1.getPriority());
System.out.println("Priority of th2(|) : " + th2.getPriority());
th1.start();
th2.start();
}
}
class ThreadEx8_1 extends Thread {
public void run() {
for (int i = 0; i < 300; i++ ) {
System.out.print("-");
for(int x = 0; x < 10000000; x++);
}
}
}
class ThreadEx8_2 extends Thread {
public void run() {
for (int i = 0; i < 300; i++) {
System.out.print("|");
for (int x = 0; x < 10000000; x++);
}
}
}
main()
에서 생성했으므로 main 메서드를 실행하는 쓰레드의 우선순위인 5를 상속받았다.th2.setPriority(7)
로 th2의 우선순위를 7로 변경했다. for (int i = 0; i < 300; i++ ) {
System.out.print("-");
for(int x = 0; x < 10000000; x++); // 작업을 지연시키기 위한 for문
}
Priority of th1(-) : 5
Priority of th2(|) : 7
-||-
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||-------------------------------------------------------------------
-------------------------------------------------------------------------
-------------------------------------------------------------------------
-------------------------------------------------------------------------
------------
Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
main()
을 수행하는 main쓰레드는 main 쓰레드 그룹에 속한다.ThreadGroup getThreadGroup()
: 쓰레드 자신이 속한 쓰레드 그룹을 반환한다.void uncaughException(Thread t, Throwable e)
: 쓰레드 그룹의 쓰레드가 처리되지 않은 예외에 의해 실행이 종료되었을 때, JVM에 의해 이 메서드가 자동적으로 호출된다.public class ThreadEx9 {
public static void main(String[] args) {
ThreadGroup main = Thread.currentThread().getThreadGroup();
ThreadGroup grp1 = new ThreadGroup("Group1");
ThreadGroup grp2 = new ThreadGroup("Group2");
// ThreadGroup(ThreadGroup parent, String name)
ThreadGroup subGrp1 = new ThreadGroup(grp1, "subGroup1");
grp1.setMaxPriority(3); // 쓰레드 그룹 grp1의 최대 우선순위를 3으로 변경
Runnable r = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000); // 쓰레드를 1초간 멈추게 한다.
} catch (InterruptedException e) {
}
}
};
// Thread(ThreadGroup parent, Runnable r, String name)
new Thread(grp1, r, "th1").start();
new Thread(subGrp1, r, "th2").start();
new Thread(grp2, r, "th3").start();
System.out.println(">>List of ThreadGroup : " + main.getName()
+ ", Active threadGroup: "+ main.activeGroupCount()
+ ", Active Thread: " + main.activeCount());
main.list();
}
}
>>List of ThreadGroup : main, Active threadGroup: 3, Active Thread: 4
java.lang.ThreadGroup[name=main,maxpri=10]
Thread[main,5,main]
java.lang.ThreadGroup[name=Group1,maxpri=3]
Thread[th1,3,Group1]
java.lang.ThreadGroup[name=subGroup1,maxpri=3]
Thread[th2,3,subGroup1]
java.lang.ThreadGroup[name=Group2,maxpri=10]
Thread[th3,5,Group2]
setMaxPriority()
는 쓰레드가 쓰레드 그룹에 추가되기 이전에 호출되어야 한다.setDaemon(true)
를 호출하기만 하면 된다.setDaemon(true)
는 반드시 start()
호출 전에 실행되어야 한다. 그렇지 않으면 IllegalThreadStateException
이 발생한다..boolean isDaemon()
void setDeaemon(boolean on)
public class ThreadEx10 implements Runnable {
static boolean autoSave = false;
public static void main(String[] args) {
Thread t = new Thread(new ThreadEx10());
t.setDaemon(true);
t.start();
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
System.out.println(i);
if (i == 5) {
autoSave = true;
}
}
System.out.println("프로그램을 종료합니다.");
}
public void run() {
while(true) {
try {
Thread.sleep(3 * 1000); // 3초마다
} catch(InterruptedException e) {}
// autoSave의 값이 true이면 autoSave()를 호출한다.
if(autoSave) {
autoSave();
}
}
}
public void autoSave() {
System.out.println("작업 파일이 자동 저장 되었습니다.");
}
}
autoSave()
를 호출하는 일을 무한히 반복하도록 쓰레드를 생성했다.Thread t = new Thread(new ThreadEx10());
t.setDaemon(true); // 이 부분이 없으면 종료되지 않는다.
t.start();
package _ch12;
import java.util.Iterator;
import java.util.Map;
public class TreadEx11 {
public static void main(String[] args) {
ThreadEx11_1 t1 = new ThreadEx11_1("Thread1");
ThreadEx11_2 t2 = new ThreadEx11_2("Thread2");
t1.start();
t2.start();
}
}
class ThreadEx11_1 extends Thread {
ThreadEx11_1(String name) {
super(name);
}
public void run() {
try {
sleep(5 * 1000); // 5초동안 기다린다.
} catch(InterruptedException e) {}
}
}
class ThreadEx11_2 extends Thread {
ThreadEx11_2(String name) {
super(name);
}
public void run() {
Map map = getAllStackTraces();
Iterator it = map.keySet().iterator();
int x = 0;
while(it.hasNext()) {
Object obj = it.next();
Thread t = (Thread) obj;
StackTraceElement[] ste = (StackTraceElement[]) (map.get(obj));
System.out.println("[" + ++x + "] name: " + t.getName()
+ ", group :" + t.getThreadGroup().getName()
+ ", daemon : " + t.isDaemon());
for (int i = 0; i < ste.length; i++) {
System.out.println(ste[i]);
}
System.out.println();
}
}
}
[1] name: DestroyJavaVM, group :main, daemon : false
[2] name: Attach Listener, group :system, daemon : true
[3] name: Signal Dispatcher, group :system, daemon : true
[4] name: Thread1, group :main, daemon : false
[5] name: Thread2, group :main, daemon : false
java.base@17.0.7/java.lang.Thread.dumpThreads(Native Method)
java.base@17.0.7/java.lang.Thread.getAllStackTraces(Thread.java:1662)
app//_ch12.ThreadEx11_2.run(TreadEx11.java:35)
[6] name: Common-Cleaner, group :InnocuousThreadGroup, daemon : true
java.base@17.0.7/java.lang.Object.wait(Native Method)
java.base@17.0.7/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155)
java.base@17.0.7/jdk.internal.ref.CleanerImpl.run(CleanerImpl.java:140)
java.base@17.0.7/java.lang.Thread.run(Thread.java:833)
java.base@17.0.7/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:162)
[7] name: Notification Thread, group :system, daemon : true
[8] name: Reference Handler, group :system, daemon : true
java.base@17.0.7/java.lang.ref.Reference.waitForReferencePendingList(Native Method)
java.base@17.0.7/java.lang.ref.Reference.processPendingReferences(Reference.java:253)
java.base@17.0.7/java.lang.ref.Reference$ReferenceHandler.run(Reference.java:215)
[9] name: Finalizer, group :system, daemon : true
java.base@17.0.7/java.lang.Object.wait(Native Method)
java.base@17.0.7/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155)
java.base@17.0.7/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:176)
java.base@17.0.7/java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:172)
getAllStackTraces()
NEW
: 쓰레드가 생성되고 아직 start()
가 호출되지 않은 상태RUNNABLE
: 실행 중 또는 실행 가능한 상태BLOCKED
: 동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태)WAITING, TIMED_WAITING
TERMINATED
: 쓰레드의 작업이 종료된 상태 start()
를 호출하면 실행대기(RUNNABLE
)상태가 된다. 먼저온 쓰레드 뒤에 줄을 선다.yield()
를 만나면 다시 실행대기상태가 되고 다음 차례의 쓰레드가 실행상태가 된다.suspend()
, sleep()
, wait()
, join()
, I/O block
에 의해 일시정지 상태가 될 수 있다.I/O block
은 입출력작업에서 발생하는 지연상태를 말한다. 사용자의 입력을 기다리는 경우를 예로 들 수 있는데, 이런 경우 일시정지 상태에 있다가 사용자가 입력을 마치면 다시 실행대기 상태가 된다.notify()
, resume()
, interrupted()
가 호출되면 일시정지 상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다.stop()
이 호출되면 쓰레드는 소멸된다.쓰레드를 생성해 start()
를 호출하면 줄을 서고, 자기 차례가 되면 실행되고, 시간이 끝나면 다시 줄을 서는 것을 반복한다. 그러다가 자신의 작업이 종료되면 소멸된다. 이게 기본 프로세스이고, 중간중간 스레드가 멈추는 경우가 있는데, suspend, sleep, wait, join, I/O block으로 일시정지 상태가 될 수 있다.
설명을 위해 1에서 6까지 번호를 붙이긴 했지만 번호의 순서대로 쓰레드가 수행되는 것은 아니다.
쓰레드의 실행을 제어할 수 있는 메서드가 제공된다. 이들을 활용해서 효율적인 프로그램을 작성할 수 있다.
static void sleep(long millis)
static void sleep(long millis, int nanos)
void join()
: 다른 쓰레드 기다리기void join(long millis)
void join(long millis, int nanos)
join()
을 호출한 쓰레드로 다시 돌아와 실행을 계속한다.void interrupt()
: 자거나 기다리는 쓰레드 깨우기sleep()
이나 join()
에 의해 일시정지상태인 쓰레드를 깨워서 실행대기 상태로 만든다. 해당 쓰레드에서는 InterruptedException
이 발생함으로써 일시 정지 상태를 벗어나게 된다.void stop()
void suspend()
일시정지resume()
을 호출하면 다시 실행 대기 상태가 된다.void resume()
재개suspend()
에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다.static void yield()
양보static이 붙은 메서드들은 쓰레드 자기 자신에게만 호출이 가능하다.
resume(), stop(), suspend()는 쓰레드를 교착상태(dead-lock)로 만들기 쉽기 때문에 deprecated 되었다.
static void sleep(long millis) // 천분의 일초 단위
static void sleep(long millis, int nanos) // 천분의 일초 + 나노초
try {
Thread.slee(1, 500000); // 쓰레드를 0.0015초동안 멈추게 한다.
} catch(InterruptedException e) {}
void delay(long millis) {
try {
Thread.sleep(millis);
} catch(InterruptedException e) {}
public class ThreaEx12 {
public static void main(String[] args) {
ThreadEX12_1 th1 = new ThreadEX12_1();
ThreadEx12_2 th2 = new ThreadEx12_2();
th1.start();
th2.start();
try {
th1.sleep(2000);
} catch (InterruptedException e) {
}
System.out.print("<<main 종료>>");
} // main
}
class ThreadEX12_1 extends Thread {
public void run() {
for (int i = 0; i < 300; i++) {
System.out.print("-");
}
System.out.print("<<th1 종료>>");
} // run()
}
class ThreadEx12_2 extends Thread {
public void run() {
for (int i = 0; i < 300; i++) {
System.out.print("|");
}
System.out.print("<<th2 종료>>");
} // run()
}
||-----------||||--------------------------------------------------
|||||||||||||||||||||||||||||||||||||||--|||||||||||||||||||||-----------
-------------------------------------------------------------------------
-----------------------------------------||||||||||||||||||||||--------
||---------------------
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||------------------------|||||||||||||||----------------
-------------------||||||||||||------||-----|||||||||||||-------------
<<th1 종료>>|||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||<<th2 종료>><<main 종료>>
th1.sleep(2000);
코드를 실행했음에도 th1이 먼저 종료되었다.sleep()
이 항상 현재 실행중인 쓰레드에 작동하기 때문에 th1.sleep(2000);
와 같은 형태로 호출하였어도 실제로 영향을 받는 것은 main메서드를 실행하는 main 쓰레드이기 때문이다.Thread.sleep(2000);
과 같이 호출되어야 한다.yield()
의 경우에도 동일하다.interrupt()
interrupt()
를 실행할 수 있다.interrupted
상태(인스턴스 변수)를 변경하는 것이다.void interrupt() 쓰레드의 interrupted 상태를 false에서 true로 변경.
boolean isInterrupted() 쓰레드의 interrupted 상태를 반환.
static boolean interrupted() 현재 쓰레드의 interrupted 상태를 알려주고, false로 초기화
interrupted()
interrupt()
가 호출되었는지 알려준다.interrupt()
가 호출된 경우 true, 호출되지 않은 경우 false를 반환한다.interrupt()
가 실행될 수 있도록 다시 false로 초가화하는 기능을 수행한다.Thead th = new Thread();
th.start();
...
th.interrupt(); // 쓰레드 th에 interrupt()를 호출한다.
class MyThread extends Thread {
public void run() {
while(!interrupted()) { // interrupted()의 결과가 false인 동안 반복
...
}
}
}
public class ThreadEx13 {
public static void main(String[] args) throws Exception {
ThreadEx13_1 th1 = new ThreadEx13_1();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + "입니다.");
th1.interrupt(); // interrupt()를 호출하면, interrupted 상태가 true가 된다.
System.out.println("isInterrupted(): " + th1.isInterrupted()); // true
}
}
class ThreadEx13_1 extends Thread {
public void run() {
int i = 10;
while(i != 0 && !isInterrupted()) {
System.out.println(i--);
int count = 0;
for (long x = 0; x < 2500000000L; x++) {
count += x;// 시간 지연
}
}
System.out.println("카운트가 종료되었습니다.");
}
}
10
9
8
7
입력하신 값은 123456입니다.
isInterrupted(): true
카운트가 종료되었습니다.
public class ThreadEx14 {
public static void main(String[] args) throws Exception {
ThreadEx14_1 th1 = new ThreadEx14_1();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + "입니다.");
th1.interrupt(); // interrupt()를 호출하면, interrupted 상태가 true가 된다.
System.out.println("isInterrupted(): " + th1.isInterrupted());
}
}
class ThreadEx14_1 extends Thread {
public void run() {
int i = 10;
while(i != 0 && !isInterrupted()) {
System.out.println(i--);
try {
Thread.sleep(1000); // 1초 지연
} catch(InterruptedException e) {}
}
System.out.println("카운트가 종료되었습니다.");
}
}
10
9
8
입력하신 값은 123456789입니다.
isInterrupted(): flase // <- true일때도 있음
7
6
5
4
3
2
1
카운트가 종료되었습니다.
Thread.sleep(1000);
로 1초동안 지연되도록 변경하자 카운트가 종료되지 않았다.Thread.sleep(1000);
에서 InterruptedException
이 발생했기 때문이다.sleep()
에 의해 쓰레드가 잠시 멈춰있을 때, interrupt()
를 호출하면 InterruptedException
이 발생되고 쓰레드의 interrupted 상태는 false로 자동 초기화된다.interrupted()
를 추가로 넣어줘서 쓰레드의 interrupted
상태를 true로 다시 바꿔줘야 한다.suspend()
: 쓰레드를 일시정지시킨다.resume()
: suspend()에 의해 일시정지된 쓰레드를 실행대기상태로 만든다.suspend()
보다 먼저 호출되면 쓰레드는 영원히 실행 가능 상태가 되지 않는다.stop()
: 쓰레드를 즉시 종료시킨다.public class ThreadEx15 {
public static void main(String[] args) {
RunImplEx15 r = new RunImplEx15();
Thread th1 = new Thread(r, "*");
Thread th2 = new Thread(r, "**");
Thread th3 = new Thread(r, "***");
th1.start();
th2.start();
th3.start();
try {
Thread.sleep(2000);
th1.suspend(); // 쓰레드 th1을 잠시 중단시킨다.
Thread.sleep(2000);;
th2.suspend();
Thread.sleep(3000);
th1.resume(); // 쓰레드 th1이 다시 동작하도록 한다.
Thread.sleep(3000);
th1.stop(); // 쓰레드 th1을 강제종료시킨다.
th2.stop();
Thread.sleep(2000);
th3.stop();
} catch (InterruptedException e) {};
}
}
class RunImplEx15 implements Runnable {
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
} //run()
}
***
**
*
***
**
*
*** // th1이 suspend되었다.
**
***
**
***
***
***
* // th1이 resume되었다.
***
*
***
* // th1이 stop되었다.
***
***
***
package _ch12;
public class ThreadEx16 {
public static void main(String[] args) {
RunImplEx16 r1 = new RunImplEx16();
RunImplEx16 r2 = new RunImplEx16();
RunImplEx16 r3 = new RunImplEx16();
Thread th1 = new Thread(r1, "*");
Thread th2 = new Thread(r2, "**");
Thread th3 = new Thread(r3, "***");
th1.start();
th2.start();
th3.start();
try {
Thread.sleep(2000);
r1.suspend();
Thread.sleep(2000);
r2.suspend();
Thread.sleep(3000);
r1.resume();
Thread.sleep(3000);
r1.stop();
r2.stop();
Thread.sleep(2000);
r3.stop();
} catch (InterruptedException e) {}
}
}
class RunImplEx16 implements Runnable {
boolean suspended = false;
boolean stopped = false;
public void run() {
while(!stopped) {
if(!suspended) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch(InterruptedException e) {}
}
}
System.out.println(Thread.currentThread().getName() + " - stopped");
}
public void suspend() {
suspended = true;
}
public void resume() {
suspended = false;
}
public void stop() {
stopped = true;
}
}
stopped
와 suspended
라는 boolean 타입의 두 변수를 인스턴스 변수로 선언하고, 이 변수를 사용해 반복문과 조건문의 조건식으로 사용한다.volatile
을 붙이자.volatile boolean suspedned = false;
volatile boolean stopped = false;
package _ch12;
public class ThreadEx17 {
public static void main(String[] args) {
ThreadEx17_1 th1 = new ThreadEx17_1("*");
ThreadEx17_1 th2 = new ThreadEx17_1("**");
ThreadEx17_1 th3 = new ThreadEx17_1("***");
th1.start();
th2.start();
th3.start();
try {
Thread.sleep(2000);
th1.suspend();
Thread.sleep(2000);
th2.suspend();
Thread.sleep(3000);
th1.resume();
Thread.sleep(3000);
th1.stop();
th2.stop();
Thread.sleep(2000);
th3.stop();
} catch (InterruptedException e) {}
}
}
class ThreadEx17_1 implements Runnable {
boolean suspended = false;
boolean stopped = false;
Thread th;
ThreadEx17_1(String name) {
th = new Thread(this, name); // Thread(Runnable r, String name)
}
public void run() {
while(!stopped) {
if(!suspended) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
System.out.println(Thread.currentThread().getName() + " - stopped");
}
public void suspend() {
suspended = true;
}
public void resume() {
suspended = false;
}
public void stop() {
stopped = true;
}
public void start() {
th.start();
}
}
*
**
***
**
*
***
**
***
**
***
**
***
***
***
*
***
*
***
*
***
** - stopped
* - stopped
***
***
*** - stopped
join()
을 사용한다.void join() // 작업이 모두 끝날때까지
void join(long millis) // 천분의 일초 동안
void join(long millis, int nanos) // 천분의 일초 + 나노초 동안
try {
th1.join(); // 현재 실행중인 쓰레드가 쓰레드 th1의 작업이 끝날때까지 기다린다.
} catch (InterruptedException e) {
}
public class ThreadEx19 {
static long startTime = 0;
public static void main(String[] args) {
ThreadEx19_1 th1 = new ThreadEx19_1();
ThreadEx19_2 th2 = new ThreadEx19_2();
th1.start();
th2.start();
startTime = System.currentTimeMillis();
try {
th1.join(); // main 쓰레드가 th1의 작업이 끝날때까지 기다린다.
th2.join(); // main 쓰레드가 th2의 작업이 끝날때까지 기다린다.
} catch (InterruptedException e) {
}
System.out.println("소요시간: " + (System.currentTimeMillis() - ThreadEx19.startTime));
}
}
class ThreadEx19_1 extends Thread {
public void run() {
for (int i = 0; i < 300; i++) {
System.out.print(new String("-"));
}
} // run()
}
class ThreadEx19_2 extends Thread {
public void run() {
for (int i = 0; i < 300; i++) {
System.out.print(new String("|"));
}
} // run()
}
join()
으로 쓰레드 th1과 th2의 작업이 마칠때까지 main쓰레드가 기다리도록 했다.public class ThreadEx20 {
public static void main(String[] args) {
ThreadEx20_1 gc = new ThreadEx20_1();
gc.setDaemon(true);
gc.start();
int requiredMemory = 0;
for (int i = 0; i < 20; i++) {
requiredMemory = (int)(Math.random() * 10) * 20;
// 필요한 메모리가 사용할 수 있는 양보다 크거나 전체 메모리의 60% 이상을
// 사용했을 경우 gc를 깨운다.
if (gc.freeMemory() < requiredMemory
|| gc.freeMemory() < gc.totalMemory() * 0.4) {
gc.interrupt(); // 잠자고 있는 쓰레드 gc를 깨운다.
}
gc.usedMemory += requiredMemory;
System.out.println("usedMemory: " + gc.usedMemory);
}
}
}
class ThreadEx20_1 extends Thread {
final static int MAX_MEMORY = 1000;
int usedMemory = 0;
public void run() {
while(true) {
try {
Thread.sleep(10 * 1000); // 10초를 기다린다.
} catch (InterruptedException e) {
System.out.println("Awaken by interrupt().");
}
gc(); // garbage collection을 수행한다.
System.out.println("Garbage Collected. Free Memory :" + freeMemory());
}
}
public void gc() {
usedMemory -= 300;
if(usedMemory < 0) {
usedMemory = 0;
}
}
public int totalMemory() {
return MAX_MEMORY;
}
public int freeMemory() {
return MAX_MEMORY - usedMemory;
}
}
random()
을 사용했기 때문에 실행할 때마다 결과가 다를 수 있다.sleep()
을 이용해 10초마다 한 번씩 가비지 컬렉션을 수행하는 쓰레드를 만든 다음, 쓰레드를 생성해 데몬 쓰레드로 설정했다.gc()
를 수행했다.usedMemory: 160
usedMemory: 260
usedMemory: 360
usedMemory: 440
usedMemory: 620
usedMemory: 700
usedMemory: 780
usedMemory: 860
usedMemory: 920
usedMemory: 980
usedMemory: 1040
usedMemory: 1140
usedMemory: 1180
usedMemory: 1340
usedMemory: 1360
usedMemory: 1480
usedMemory: 1640
usedMemory: 1640
usedMemory: 1760
usedMemory: 1820
Awaken by interrupt().
Garbage Collected. Free Memory :-520
MAX_MEMORY
가 1000임에도 불구하고 중간에 값이 1000을 넘는 경우가 발생한다.gc()
쓰레드가 interupt()
에 의해 깨어난 이후 gc()
수행 이전 main쓰레드의 작업이 수행되어 메모리를 사용(gc.usedMemory += requiredMemory;
)했기 때문이다.join()
을 호출해 쓰레드 gc가 작업할 시간을 어느 정도 주고 main쓰레드가 기다리도록 해서, 사용할 수 있는 메모리가 확보된 다음 작업을 계속하는 것이 필요하다.if(gc.freeMemory() < requiredMemory...
gc.interrupt();
try {
gc.join(100);
} catch(InterruptedException e) {}
}
sleep()
을 이용해서 주기적으로 실행되도록 하다가 필요할 때마다 interrupt()
를 호출해서 즉시 가비지 컬렉션이 이루어지도록 하는 것이 좋다.join()
도 함께 사용해야 한다는 것을 기억하자.yield()
가 발생한다는 보장은 없다. (위에서 언급한 것처럼 초 개념이 명확히 나뉘는게 아니다.)yield()
와 interrupt()
를 적절히 사용하면, 응답성과 효율을 높일 수 있다.yield()
를 호출해 남은 실행시간을 while문에서 낭비하지 않고 다른 쓰레드에게 양보(yield)할 수 있다.while(!stopped) { // true
if(!suspended) { // false
...
try {
Thread.sleep(1000);
} catch(InterruptedException e) {}
} // if문이 생략되고 의미없는 while문의 수행이 반복된다.
}
while(!stopped) { // true
if(!suspended) { // false
...
try {
Thread.sleep(1000);
} catch(InterruptedException e) {}
} else { // suspend인 경우 다른 쓰레드에게 실행을 양보한다.
Thread.yield();
}
}
suspend()
와 stop()
에 interrupt()
호출 코드를 추가한다.stop()
이 호출되었을 때 Thread.sleep(1000)
에 의해 쓰레드가 일시정지 상태에 머물러있는 상황이라면, stopped
의 값이 true로 바뀌었어도 쓰레드가 정지될 때까지 최대 1초의 시간지연이 생기게 된다.InterruptedException
이 발생해 즉시 일시정지 상태에서 벗어나게 되므로 응답성이 좋아진다.public void suspend() {
suspended = true;
th.interrupt();
}
public void stop() {
stopped = true;
th.interrupt();
}
public class ThreadEx18 {
public static void main(String[] args) {
ThreadEx18_1 th1 = new ThreadEx18_1("*");
ThreadEx18_1 th2 = new ThreadEx18_1("**");
ThreadEx18_1 th3 = new ThreadEx18_1("***");
th1.start();
th2.start();
th3.start();
try {
Thread.sleep(2000);
th1.suspend();
Thread.sleep(2000);
th2.suspend();
Thread.sleep(3000);
th1.resume();
Thread.sleep(3000);
th1.stop();
th2.stop();
Thread.sleep(2000);
th3.stop();
} catch(InterruptedException e) {}
}
}
class ThreadEx18_1 implements Runnable {
boolean suspended = false;
boolean stopped = false;
Thread th;
ThreadEx18_1(String name) {
th = new Thread(this, name); // Thread(Runnable r, String name)
}
public void run() {
String name = th.getName();
while(!stopped) {
if(!suspended) {
System.out.println(name);
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
System.out.println(name + " - interuupted");
}
} else {
Thread.yield();
}
}
System.out.println(name + " - stopped");
}
public void suspend() {
suspended = true;
th.interrupt();
System.out.println(th.getName() + " - interrupted() by suspended()");
}
public void stop() {
stopped = true;
th.interrupt();
System.out.println(th.getName() + " - interrupted() by stop()");
}
public void resume() {
suspended = false;
}
public void start() {
th.start();
}
}
java.util.concurrent.locsk
와 java.util.concurrent.atomic
패키지를 통해 다양한 방식으로 동기화 구현을 지원한다.public synchronized void calcSum() {
// ...
}
synchronized
메서드가 호출된 시점부터 해당 메서드가 포함된 객체의 lock을 얻어 작업을 수행하다가 메서드가 종료되면 lock을 반납한다.synchronized(객체의 참조변수) {
// ...
}
{}
으로 감싸고 블럭 앞에 synchronized(참조변수)
를 붙인다.public class ThreadEx21 {
public static void main(String[] args) {
Runnable r = new RunnableEx21();
new Thread(r).start(); // TreadGroup에 의해 참조되므로 gc 대상이 아니다.
new Thread(r).start(); // TreadGroup에 의해 참조되므로 gc 대상이 아니다.
}
}
class Account {
private int balance = 1000;
public int getBalance() {
return balance;
}
public void withdraw(int money) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch(InterruptedException e) {}
balance -= money;
}
} // withdraw
}
class RunnableEx21 implements Runnable {
Account acc = new Account();
public void run() {
while(acc.getBalance() > 0) {
// 100, 200, 300중의 한 값을 임의로 선택해서 출금(withdraw)
int money = (int)(Math.random() * 3 + 1) * 100;
acc.withdraw(money);
System.out.println("balance: " + acc.getBalance());
}
}
}
balance: 500
balance: 500
balance: 200
balance: 200
balance: -100
balance: -200
balance >=money
이 true여서 출금(balance -= money
)을 진행하려던 순간 다른 쓰레드에게 제어권이 넘어가 다른 쓰레드가 300을 출금해 발생한 일이다.Thread.sleep(1000)
을 사용해 if문을 통과하자마자 다른 쓰레드에게 제어권을 넘기도록 했지만, 굳이 이렇게 하지 않더라도 이처럼 쓰레드의 작업이 다른 쓰레드에 의해 영향을 받는 일이 발생할 수 있다. public synchronized void withdraw(int money) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch(InterruptedException e) {}
balance -= money;
}
}
public void withdraw(int money) {
synchronized(this) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch(InterruptedException e) {}
balance -= money;
}
}
} // withdraw
withdraw()
가 호출되면 이 메서드가 종료되어 lock이 반납될 때까지 다른 쓰레드는 withdraw()
를 호출하더라도 대기상태에 ㅓ물게 된다.private
여야 한다. private
가 아닌 경우 외부에서 직접 접근할 수 있어 아무리 동기화를 해도 이 값의 변경을 막을 길이 없다.balance: 800
balance: 600
balance: 500
balance: 400
balance: 100
balance: 0
balance: 0
wait()
, notify()
, notifyAll()
wait()
: 객체의 lock을 풀고 쓰레드를 해당 객체의 waiting pool에 넣는다.notify()
: waiting pool에서 대기중인 쓰레드 중의 하나를 깨운다.notifyAll()
: (특정 객체의) waiting pool에서 대기중인 모든 쓰레드를 깨운다.class Account {
int balance = 1000;
public synchronized void withdraw(int money) {
while(balance < money) {
try {
wait(); // 대기 - 락을 풀고 기다린다. 통지를 받으면 락을 재획득(ReEnterance)
} catch(InterruptedException e) {}
}
balacne -= money;
} // withdraw
public synchronized void deposit(int money) {
balance += money;
notify(); // 통지 - 대기중인 쓰레드 중 하나에게 알림.
}
}
Table
: dishes라는 ArrayList
를 가진다. (ArrayList는 동기화가 되어있지 않다.)add()
: Table에 음식(dish)를 추가하는 메서드.remove()
: Table에 음식({dish)을 제거하는 메서드Cook
: Table에 음식(dish)을 추가하는 일을 한다.Customer
: Table에 음식(dish)을 먹는 일을 한다.public class ThreadWaitEx1 {
public static void main(String[] args) throws Exception{
Table table = new Table(); // 여러 쓰레드가 공유하는 객체
new Thread(new Cook(table), "COOK1").start();
new Thread(new Customer(table, "donut"), "CUST1").start();
new Thread(new Customer(table, "burger"), "CUST2").start();
Thread.sleep(100); // 0.1초(100밀리 세컨드) 후에 강제종료 시킨다.
System.exit(0); // 프로그램 전체 종료 (모든 쓰레드가 종료됨)
}
}
class Customer implements Runnable {
private Table table;
private String food;
Customer(Table table, String food) {
this.table = table;
this.food = food;
}
public void run() {
while(true) {
try {
Thread.sleep(10);
} catch(InterruptedException e) {}
String name = Thread.currentThread().getName();
if(eatFood()) {
System.out.println(name + " ate a " + food);
} else {
System.out.println(name + " failed to eat. :(");
}
}// while
}
boolean eatFood() {
return table.remove(food);
}
}
class Cook implements Runnable {
private Table table;
Cook(Table table) {
this.table = table;
}
public void run() {
while(true) {
// 임의의 요리를 하나 선택해서 table에 추가한다.
int idx = (int) (Math.random() * table.dishNum());
table.add(table.dishNames[idx]);
try {
Thread.sleep(1);
} catch(InterruptedException e) {}
}
}
}
class Table {
String[] dishNames = { "donut", "donut", "brger" }; // donut이 더 자주 나온다.
final int MAX_FOOD = 6; // 테이블에 놓을 수 있는 최대 음식의 수
private ArrayList<String> dishes = new ArrayList<String>();
public void add(String dish) {
// 테이블에 음식이 가득찼으면, 테이블에 음식을 추가하지 않는다.
if(dishes.size() >= MAX_FOOD) {
return;
}
dishes.add(dish);
System.out.println("Dishes: " + dishes.toString());
}
public boolean remove(String dishName) {
// 지정된 요리와 일치하는 요리를 테이블에서 치운다.
for (int i = 0; i < dishes.size(); i++) {
if (dishName.equals(dishes.get(i))) {
dishes.remove(i);
return true;
}
}
return false;
}
public int dishNum() {
return dishNames.length;
}
}
ConcurrentModificationException
ArrayList
읽기 수행 중 add()
나 remove()
등의 변경이 실행되면 발생할 수 있는 예외이다.IndexOutOfBoundsException
remove()
시행)해서 예외가 터지게 된다.add()
와 remove()
를 synchronized
로 동기화해야 한다. public synchronized void add(String dish) { // synchronized 추가
if(dishes.size() >= MAX_FOOD) {
return;
}
dishes.add(dish);
System.out.println("Dishes: " + dishes.toString());
}
public boolean remove(String dishName) {
synchronized(this) {
while(dishes.size() == 0) { // 0.5초마다 음식이 추가되었는지 확인한다.
String name = Thread.currentThread().getName();
System.out.println(name + " is waiting.");
try { Thread.sleep(500); } catch(InterruptedException e) {}
}
// 지정된 요리와 일치하는 요리를 테이블에서 치운다.
for (int i = 0; i < dishes.size(); i++) {
if (dishName.equals(dishes.get(i))) {
dishes.remove(i);
return true;
}
}
}// synchronized
return false;
}
Dishes: [donut]
CUST2 failed to eat. :( // donut이 없어서 먹지 못했다.
CUST1 ate a donut
CUST1 is waiting. // 음식이 없어서 테이블에 lock을 건 채로 계속 기다리고 있다.
CUST1 is waiting.
CUST1 is waiting.
CUST1 is waiting.
...
public synchronized void add(String dish) {
while (dishes.size() >= MAX_FOOD) {
String name = Thread.currentThread().getName();
System.out.println(name + " is waiting.");
try {
wait(); // COOK 쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
}
dishes.add(dish);
notify(); // 기다리고 있는 CUST를 깨우기 위함.
System.out.println("Dishes: " + dishes.toString());
}
wait()
)하고, 음식을 추가하고 나면 손님에게 통보(notify()
)한다. public void remove(String dishName) {
synchronized(this) {
String name = Thread.currentThread().getName();
while(dishes.size() == 0) {
System.out.println(name + " is waiting.");
try {
wait(); // CUST 쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
}
while(true) {
for (int i = 0; i < dishes.size(); i++) {
if (dishName.equals(dishes.get(i))) {
dishes.remove(i);
notify(); // 잠자고 있던 COOK을 깨우기 위함
return;
}
} // for문의 끝
try {
System.out.println(name + " is waiting.");
wait(); // 원하는 음식이 없는 CUST 쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
} // while(true)
} // synchronized
}
wait()
)하고, 음식을 먹고나면 요리사에게 통보(notify()
)한다.notify()
대신 notifyAll()
을 사용할 수 있다.멀티코어 프로세서에서는 코어마다 별도의 캐시를 가지고 있다. 코어는 메모리에서 읽어온 값을 캐시에 저장하고 캐시에서 값을 읽어서 작업한다. 다시 같은 값을 읽어올 때는 먼저 캐시에 값이 있는지 확인하고 없는 경우에만 메모리에서 읽어온다. 그러다보니 도중에 메모리에 저장된 변수의 값이 변경되었는데도 캐시에 저장된 값이 갱신되지 않아서 메모리에 저장된 값이 다른 경우가 발생한다.
volatile boolean suspended = false;
volatile boolean stopped = false;
그러나 변수 앞에 volatile을 붙이면 코어가 변수의 값을 읽어올때 캐시가 아닌 메모리에서 읽어오기 때문에 캐시와 메모리간의 값을 불일치가 해결된다.
변수에 volatile을 붙이는 대신 synchronized블럭을 사용해도 같은 효과를 얻을 수 있다. 쓰레드가 synchronized 블럭으로 들어갈 때와 나올 때, 캐시와 메모리간의 동기화가 이루어지기 때문에 값을 불일치가 해소되기 때문이다.
public synchronized void stop() {
stopped = true;
}