개념
Process
실행 중인 프로그램을 의미함. 즉, 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중인 것을 말함. 이 프로세스는 프로그램에 사용되는 데이터, 메모리 등의 자원과 스레드로 구성됨.
Thread
스레드란 프로세스 내에서 실제로 작업을 수행하는 주체를 의미. 모든 프로세스에는 한 개 이상의 스레드가 존재. 한 개의 스레드를 가질 경우 싱글 스레드, 두 개 이상의 스레드를 가지는 프로세스를 멀티스레드라고 함.
- 비용 : 하나의 새로운 프로세스 생성 > 하나의 새로운 스레드 생성
- 장점 : 시스템 자원보다 효율적으로 사용 가능, 사용자에 대한 응답성 향상, 작업이 분리되어 코드가 간결해짐.
- 단점 : 동기화(사건이나 시간을 같게 함), 교착상태, 각 스레드가 고르게 실행되는 등 프로그래밍 시 고려해야 할 사항이 많음.


*멀티 스레드의 경우 동일하게 실행되는 것 같지만 실제로는 알 수 없음.
*멀티 스레드가 싱글 스레드보다 시간이 더 걸림. context switch가 발생하기 때문.
구현과 실행
방법 ❶ Thread 클래스 상속
자바는 단일 상속만 가능하기 때문에 Thread 클래스를 상속받을 경우 다른 클래스 상속이 어렵기 때문에 잘 사용하지 않음.
class ThreadWithClass extends Thread { public void run() // thread 클래스의 run()을 오버라이딩 //작업 내용 }
ThreadWithClass thread1 = new ThreadWithClass(); // 스레드 생성 thread1.start(); // 스레드 실행 class ThreadWithClass extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println(getName()); // 현재 실행 중인 스레드의 이름을 반환함. } } }
방법 ❷ Runnable 인터페이스 구현
Runnable 클래스를 implements하기 때문에 extends는 여전히 가능하여 좀 더 유연하게 사용할 수 있음.
class ThreadWithRunnable implements Runnable { public void run() // Runnable 인터페이스의 추상메서드 run()을 구현 //작업 내용 }
Thread thread2 = new Thread(new ThreadWithRunnable()); thread2.start(); // 스레드 실행 class ThreadWithRunnable implements Runnable { public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName()); // 현재 실행 중인 스레드의 이름을 반환함. } } }
start()
- 스레드를 생성한 후에 start()를 호출해야 스레드가 작업을 시작함.
- OS 스케줄러가 실행순서를 결정, start했다고 즉시 실행되지 않음. 먼저 start했다고 먼저 시작되지 않음.
- start() 호출 → 새로운 run() 호출 → start() 역할 끝. run()은 main()과 독립적으로 생성, 수행됨.

예외 처리 - try catch
try문안의 수행할 문장들에서 예외가 발생하지 않는다면 catch문 다음의 문장들은 수행이 되지 않음. 하지만 try문안의 문장들을 수행 중 해당 예외가 발생하면 예외에 해당하는 catch문이 수행됨.
//try catch 기본 구조 try { ... } catch(예외1) { ... } catch(예외2) { ... ... }

main 스레드와 상태제어 (join)
- 실행 중인 사용자 스레드가 하나도 없을 때 프로그램은 종료됨. 다만, run() 스레드 등 다른 스레드가 존재할 경우 main 스레드가 종료되어도 프로그램은 종료되지 않음.
- join()메소드는 스레드가 멈출 때까지 기다리게 함.
class Main { public static void main(String[] args) { Plus plus = new Plus(); Minus minus = new Minus(); System.out.println("start"); plus.start(); minus.start(); try { minus.join(); } catch (Exception e) { // TODO: handle exception } System.out.println("end"); } } class Plus extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("+"); } } } class Minus extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("-"); } } }
sleep, join 등 메소드 자체가 exception 을 throw 하는 메소드는 try, catch를 이용하여 예외처리를 해야 함.
우선순위
작업 중요도에 따라 스레드의 우선순위를 다르게 하여 특정 스레드가 더 많은 작업 시간을 갖게 할 수 있음.

- getPriority()와 setPriority() 메소드를 통해 스레드의 우선순위를 반환하거나 변경할 수 있음.
- 스레드의 우선순위는 1~10까지이며, 숫자가 높을수록 우선순위 또한 높아짐.
class Main { public static void main(String[] args) { Plus plus = new Plus(); Minus minus = new Minus(); plus.setPriority(10); minus.setPriority(1); plus.start(); minus.start(); } } class Plus extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("+"); } } } class Minus extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("-"); } } }
스레드의 우선순위는 비례적인 절댓값이 아닌 상대적인 값임. 무조건 우선순위가 높다고 더 빨리 끝나지 않음.
데몬 스레드
- 멀티태스킹 운영 체제에서 데몬은 사용자가 직접적으로 제어하지 않고, 백그라운드에서 돌면서 여러 작업을 하는 프로그램을 말함.
- 일반 스레드의 작업을 돕는 보조 역할 수행.
- 일반 스레드가 종료되면 자동으로 종료됨.
- ex) 가비지 컬렉터, 자동저장, 화면 자동갱신
사용법
public void run() { while(true) { // 무한루프지만 일반 스레드가 종료되면 자동으로 종료됨. try { Thread.sleep(3*1000); // 3초 마다 } catch(InterruptedException e) {} // autoSave의 값이 true면 autoSave()를 호출함. if (autoSave) { autoSave(); } } }
class daemonex implements Runnable { static boolean autoSave = false; public static void main(string[] args) { Thread t = new Thread(new daemonex()); 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); } catch (InterruptedException e) { } if (autoSave) autoSave(); // autoSave 값이 true면 autoSave()를 호출. } } public void autoSave() { System.out.println("자동저장됨."); } }
- boolean isDaemon() - 스레드가 데몬 스레드라면 true를 반환.
- void setDaemon(boolean on) - 스레드로 데몬 스레드로 변경. 매개 변수 on을 true로 지정하면 데몬 스레드가 됨.
- setDaemon(boolean on)은 반드시 start()를 호출하기 전에 실행되어야 함.
인간과 컴퓨터
Computer macbook = new Computer("MacBookPro_15"); Computer samsungOld = new Computer("samsungOld_13"); Human lee = new Human(macbook); Human lim = new Human(samsungOld); lee.doCoding(); lim.doCoding(); System.out.println("macbook.getName() : " + macbook.getName());
public class Computer { private String name; public Computer(String _name) { name = _name; } public String getName() { return name; } }
public class Human { private Computer computerInstance = null; public Human(Computer computerInstanceFromOutside) { computerInstance = computerInstanceFromOutside; } public void doCoding() { System.out.println("I'm coding with my computer " + computerInstance.getName()); } }
Counter와 Printer
cass Main { public static void main(String[] args) { Printer printerName = new Printer(); Thread counterThread = new Thread(new Counter(printerName)); Thread printThread = new Thread(printerName); counterThread.start(); printThread.start(); } }
public class Counter implements Runnable { private int n = 1; private int maxNumber = 10; private Printer printerOfCounter; public Counter(Printer oPrinterName) { printerOfCounter = oPrinterName; } private void countNumber() { while (n <= maxNumber) { try { Thread.sleep(1000); } catch (Exception e) { // TODO: handle exception } System.out.println(n); n++; } } @Override public void run() { countNumber(); printerOfCounter.stopPrint(); } }
public class Printer implements Runnable { private boolean canPrint = true; private void printMessage() { try { Thread.sleep(3 * 1000); } catch (Exception e) { // TODO: handle exception } System.out.println("auto saved"); } public void stopPrint() { canPrint = false; } @Override public void run() { while (canPrint) { printMessage(); } } }
스레드를 이용한 매수, 매도 프로그램
Seller와 Buyer 클래스를 스레드로 만들었다. 따라서 메인이 흘러가면서 동시에 매도, 매수가 이뤄지도록 했다
- 미해결 : 동기화 부분 해결하기
public class Main { public static void main(String[] args) { //samsung, KRW 인스턴스 생성 Asset[] asset = new Asset[] { new Samsung(20), new KRW(1600000) }; //현황 출력 int totalAsset = 0; int stockSum = 0; int cashSum = 0; for (int i = 0; i < asset.length; i++) { totalAsset += asset[i].getTotalValue(); if (asset[i] instanceof Stock) { stockSum += asset[i].getTotalValue(); } else if (asset[i] instanceof Cash) { cashSum += asset[i].getTotalValue(); } asset[i].printInfo(); } System.out.println("stockSum = " + stockSum); System.out.println("cashSum = " + cashSum); System.out.println("totalAsset = " + totalAsset); //trader 스레드 생성 Thread cathieWoodThread = new Thread(new CathieWood((Stock) asset[0], (Cash) asset[1])); Thread michealBurryThread = new Thread(new MichaelBurry((Stock) asset[0], (Cash) asset[1])); //생성한 스레드 실행 cathieWoodThread.start(); michealBurryThread.start(); } }
import java.util.Random; public class CathieWood implements Runnable { Random random = new Random(); private Stock stock; private Cash cash; //캐시 우드 생성자 public CathieWood(Stock s, Cash c) { stock = s; cash = c; } //매수 public synchronized boolean buy() { int n = random.nextInt(10); if (cash.getTotalValue() >= stock.getPrice()) { cash.sell(n * stock.getPrice()); stock.buy(n); return true; } else { System.out.println("no money to buy"); return false; } } //출력 public synchronized void reportTrading() { System.out.println("---------CathieWood Trading Report---------"); cash.printInfo(); stock.printInfo(); } public void run() { while (buy()) { reportTrading(); } } }
- 생성자 : 생성자란 인스턴스가 만들어질 때 호출되는 함수임.
public class Main { int x; public Main(int y) { x = y; } public static void main(String[] args) { Main myObj = new Main(5); System.out.println(myObj.x); } } // Outputs 5
생성자 이름은 클래스 이름과 일치해야 함. 또한 void와 같은 return 불가능.
- Synchronized : 여러 개의 스레드가 한 개의 자원을 사용하고자 할 때, 데이터를 사용하고 있는 스레드를 제외한 나머지 스레드들은 데이터에 접근 할 수 없도록 막는 역할.
한 시점에 오직 하나의 스레드만이 동기화된 인스턴스 메소드를 실행할 수 있음.
너무 남발하면 오히려 프로그램 성능 저하를 일으킬 수 있음.
- random.nextln() : 파라미터를 사용해서 원하는 범위 내의 양수 값을 가져올 수 있음.
마치며
스레드는 프로그램, 프로세스, 메모리 영역 등 컴퓨터와 관련된 내용이 많았다. 그래서 CS가 부족한 나에게 너무 힘든 과정이었다. 알면 알 수록 어려워지는 기분을 느꼈다. 질문을 하려 해도 내가 무엇을 모르는지 모르는 지경까지 도달해서 절망적이었다. 그래도 예시를 하나하나 다시 작성해보면서 감을 익혔다. 분명 이해했던 내용인데 코드로 다시 칠 땐 모르겠다. 그리고 생성자나 인스턴스 등 이미 공부했던 내용이 다시 헷갈렸다. 지금은 헷갈리는 게 덜한데 또 다른 코드를 치면 안 헷갈린다는 장담을 못 하겠다.