Java에서 쓰레드를 생성하는 방법
1. Runnable 인터페이스를 구현한 객체를 Thread 객체 생성자로 전달한다.
private void ExampleThread()
{
class Task implements Runnable
{
@Override
public void run()
{
}
}
Thread t1 = new Thread(new Task());
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
}
});
t2.start();
}
- 인터페이스 객체는 run()이라는 메서드를 구현하고 실제 바탕 작업을 의미한다.
- Runnable 인터페이스를 상속하는 익명 클래스를 전달한다.
start()는 스레드를 새로 만들어 바탕 작업을 시작한다.
2. Thread를 상속
Thread를 직접 상속하여 run() 메서드를 오버라이드 한다.
private void ExampleThread2()
{
class ThreadEx extends Thread
{
@Override
public void run()
{}
}
Thread t = new ThreadEx();
t.start();
}
스레드 이름 설정 및 가져오기 getName, setName
Thread t = new Thread();
t.setName("m1");
var s = t.getName(); // "m1"
현재 실행중인 스레드 객체 가져오기
Thread t = Thread.currentThread();
스레드의 실행 우선 순위 설정하기
Thread t = Thread.currentThread();
t.setPriority(Thread.MAX_PRIORITY);
t.setPriority(Thread.NORM_PRIORITY);
t.setPriority(Thread.MIN_PRIORITY);
synchronized 동기화 방법
synchronized(object) : object에는 락 객체 혹은 공유 객체가 올 수 있다.
var object = new Object();
synchronized(object)
{
// critical section;
}
synchronized 메서드 : 둘 이상의 스레드가 메서드를 동시에 호출할 수 없다.
public synchronized void addElement(int a)
{
set.add(a);
}
synchronized 메서드는 호출하는 객체를 잠금하는 것이므로 함수 내부에 synchronized(this) {}를 만드는 역할을 한다.
public void Example13()
{
class Sync
{
private int state = 10;
public synchronized void setAsZero()
{
System.out.println("Set As Zero Starts");
try {Thread.sleep(1000); } catch (Exception e){}
state = 0;
System.out.println("Set As " + state);
}
public synchronized void setAsOne()
{
System.out.println("Set As One Starts");
state = 1;
System.out.println("Set As " + state);
}
}
Sync sync = new Sync();
Thread t1 = new Thread(() ->
{
sync.setAsZero();
});
Thread t2 = new Thread(() ->
{
sync.setAsOne();
});
t1.start();
t2.start();
}
서로 다른 메서드를 다른 스레드에서 호출하였다하더라도 객체를 잠금하므로 반드시 하나의 synchronized 메서드가 종료되어야 다른 synchronized 메서드를 호출할 수 있다.
스레드 상태
Thread.State state = t.getState();
상태 | enum | 설명 |
생성 직후 | NEW | 객체 생성 이후 ~ start() 호출 이전 |
실행 가능 (대기 큐에 큐잉) | RUNNABLE | start()가 호출되었으나 스케줄링이 아직 되지 않음. |
일시정지 | WAITING | 통지(signal)을 받아야 RUNNABLE이 될 수 있음. |
TIMED_WAITING | 통지를 받거나 일정 시간이 지나면 RUNNABLE | |
BLOCKED | 소유하고자 하는 객체의 락의 해제를 기다림 | |
종료 | TERMINATED | 실행을 종료함. |
- sleep()의 경우 스레드를 일정시간 재우므로 TIMED_WAITING 상태로 만든다.
- wait()은 WAITING 상태로 다른 스레드에 의해 notify를 받아야 실행 대기가 가능해진다.
- yield()는 실행 중인 스레드의 타임 퀀텀을 반납하고 다시 큐잉되도록 유도한다.
class YieldThread extends Thread
{
public boolean stop = false;
public boolean work = true;
@Override
public void run()
{
while (!stop){
if (work)
{
System.out.println(getName() + " Works");
try{ Thread.sleep(1000);} catch ( Exception e ){}
}
else{
Thread.yield();
}
}
System.out.println(getName() + " Finished");
}
}
- interrupt의 경우 notify()와 비슷하게 WAITING 상태에 있는 스레드를 실행가능하게 하지만 실행 영역에서 InteruptedException 예외를 던지므로 예외처리가 필요하다.
public void Example6()
{
class SyncThread extends Thread
{
private final Object sync;
public SyncThread(Object syncObject)
{
sync = syncObject;
}
@Override
public void run()
{
try
{
Thread.sleep(1000);
}catch (Exception e) {}
synchronized (sync)
{
try
{
sync.wait();
}
catch (InterruptedException interruptedException) {
System.out.println("Interrupted");
}
catch (Exception e)
{
return;
}
}
System.out.println("Signaled");
}
}
Object sync = new Object();
Thread thread = new SyncThread(sync);
try
{
System.out.println("Thread State " + thread.getState());
thread.start();
System.out.println("Thread State " + thread.getState());
for (int i = 0; i < 4; ++i)
{
System.out.println("Thread State " + thread.getState());
Thread.sleep(500);
}
synchronized (sync)
{
try
{
System.out.println("Notify");
sync.notify();
}
catch (Exception ignored) {}
}
System.out.println("Thread State " + thread.getState());
thread.join();
System.out.println("Thread State " + thread.getState());
} catch (Exception e) {
System.out.println(e + " : " + e.getMessage());
}
}
java에서는 메서드에 throws(Exception) 이 있는 경우 메서드가 Exception 예외를 던질 것을 대비해 항상 try-catch로 감싸거나 메서드 자체도 throws로 정의하여 외부로 다시 예외를 전파하는 것을 강제한다.
- join()의 경우 해당 스레드가 종료될 때까지 caller 스레드를 WAITING 상태(일시 정지)로 만든다.
public void Example4()
{
class WorkerThread extends Thread
{
private final static HashSet<Integer> set = new HashSet<Integer>();
@Override
public void run()
{
synchronized (set)
{
for (int i = 0 ; i < 100; ++i)
{
int r = (int)(Math.random() * 100000) % 100000;
if (!set.contains(r))
{
set.add(r);
}
}
System.out.println("[" + getName() + "] " + "Get Count : " + set.size());
}
}
}
Thread threads[] = new Thread[4];
for (int i = 0; i < 4; ++i)
{
threads[i] = new WorkerThread();
threads[i].setName("Typical Thread " + i);
System.out.println(String.format("%s : state %s", threads[i].getName(), threads[i].getState()));
}
for (int i = 0 ; i < 4; ++i)
{
threads[i].start();
System.out.println(String.format("%s : state %s", threads[i].getName(), threads[i].getState()));
}
for (int i =0 ; i < 4; ++i)
{
try
{
threads[i].join();
}
catch (Exception e)
{
}
System.out.println(String.format("%s : state %s", threads[i].getName(), threads[i].getState()));
}
}
wait()와 notify()
wait()과 notify()는 공유 객체가 동기화되는 컨텍스트 (synchronized(sharedObject))에서만 호출가능하다.
wait() 혹은 notify()가 공유 객체를 소유하지 않은 상태, 즉 synchronized 컨텍스트 내에서 실행되지 않을 경우
IllegalMonitorStateException 예외를 던진다.
Object sync = new Object();
Thread thread = new SyncThread(sync);
// throws java.lang.IllegalMonitorStateException since this thread has not owned (sync) object
try {sync.wait(); } catch (InterruptedException e){}
wait()를 호출한 스레드는 sync라는 객체의 waiting pool에 대기하게 된다. 대기하게되는 스레드는 notify() 혹은 notifyAll()을 받아야 실행가능하게 된다.
wait()과 notify()는 sync 객체 소유로 동기화된 임계 영역에서 호출되므로 하나씩 통지를 하거나 받을 것이다.
public void Example6()
{
class SyncThread extends Thread
{
private final Object sync;
public SyncThread(Object syncObject)
{
sync = syncObject;
}
@Override
public void run()
{
synchronized (sync)
{
try
{
sync.wait();
}
catch (InterruptedException interruptedException) {
System.out.println("Interrupted");
}
catch (Exception e)
{
return;
}
}
System.out.println(Thread.currentThread().getName() + " Signaled");
}
}
Object sync = new Object();
ArrayList<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 8; ++i) {
Thread t = new SyncThread(sync);
t.setName("Thread " + i);
threads.add(t);
}
for (int i = 0; i < 8; ++i) {
threads.get(i).start();
}
try{Thread.sleep(2000);} catch ( Exception e) {}
synchronized (sync)
{
try
{
System.out.println("Notify");
sync.notifyAll();
}
catch (Exception ignored) {}
}
for (int i = 0; i < 8; ++i) {
try { threads.get(i).join(); } catch (Exception e){}
}
}
synchronized 컨텍스트 내에서 sleep을 호출하였을 때
synchronized (sync)
{
try{Thread.sleep(2000);} catch ( Exception e) {}
try
{
System.out.println("Notify");
sync.notifyAll();
}
catch (Exception ignored) {}
}
sync 소유권을 가진 상태에서 sleep을 호출하면 해당 스레드는 일시 정지 상태가 되지만 그렇다고 해서 sync에 대한 소유권을 반납하는 것은 아니다. sleep 메서드가 자주 사용되는 것은 아니지만 동기화된 영역에서 sync.wait()이 아닌 스레드를 일시 정지, 블락시키는 코드가 있다면 주의하도록 한다.
데몬 스레드
메인 스레드 종료시 자동으로 같이 종료하도록 하는 스레드이다. JVM도 데몬 스레드이다. 또는 백그라운드 스레드라고도 한다.
// 데몬 스레드 설정
t.setDaemon(true);
반드시 start() 호출 이전에 설정하도록 한다.
public void Example7()
{
class BackgroundWorker extends Thread
{
BackgroundWorker()
{
setDaemon(true);
}
@Override
public void run()
{
while (true) {
Work();
try{ Thread.sleep(1000);} catch (Exception e){ return;}
}
}
private void Work()
{
System.out.println("BackgroundWorker Works...");
}
}
BackgroundWorker worker = new BackgroundWorker();
worker.setName("BackgroundWorker");
worker.start();
Map<Thread, StackTraceElement[]> infos = Thread.getAllStackTraces();
Set<Thread> threads = infos.keySet();
for (var thread : threads)
{
System.out.println(String.format("Name : %s (%s) Group : %s", thread.getName(), thread.isDaemon() ? "Daemon" : "Main", thread.getThreadGroup().getName()));
}
sleepex(3000);
System.out.println("Program Ends");
}
스레드 그룹
스레드 그룹을 생성하고 새로운 스레드를 해당 스레드 그룹에 할당하여 생성할 수 있다.
그룹으로 스레드를 관리하면 interrupt와 같은 신호를 그룹에 속해있는 모든 스레드에 한 번에 보낼 수 있다.
public void Example8()
{
class MyThread extends Thread
{
public MyThread(ThreadGroup group, String name)
{
super(group, name);
}
@Override
public void run()
{
while (true)
{
try{
Thread.sleep(1000);
} catch (InterruptedException e)
{
System.out.println(getName() + " interrupted");
break;
}
}
System.out.println(getName() + " exits");
}
}
ThreadGroup group = new ThreadGroup("MyGroup");
MyThread t1 = new MyThread(group, "first");
MyThread t2 = new MyThread(group, "second");
t1.start();;
t2.start();
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
mainGroup.list();
System.out.println();
sleepex(3000);
group.interrupt();
}