
Thread는 하나의 프로세스 내부에서 독립적으로 실행되는 하나의 작업 단위를 말한다.
실행중인 프로그램을 뜻하며, 작업 관리자에 프로세스 탭에 있는 것들이 이 부분에 해당한다.
프로세스가 실행할 코드와 매크로 상수가 기계어의 형태로 저장된 공간.
-> Read - only
코드에서 선언한 전역변수 또는 static 변수 등이 저장된 공간.
heap은 런타임에 결정되고, malloc, calloc으로 heap 영역의 메모리를 사용할 수 있다.
데이터 배열의 크기가 확실하지 않고 변동이 있을 때 heap 영역을 활용해서 메모리를 할당한다.
사용하고 난 뒤에는 반드시 해제해야하며, 그렇지 않을 시 memory leak이 발생한다.
프로세스 내에서 실행되고 있는 흐름의 단위이다.
프로세스의 메모리 공간을 관리하기 위한 알고리즘 중 하나로, 함수 안에서 선언된 지역변수, 매개변수, 리턴 값 등이 저장되고 함수 호출 시 기록하고, 종료되면 제거한다.
-> LIFO 방식을 따른다.
여러 개의 프로세스를 동시에 실행하는 것을 의미한다.
-> 여러 개의 프로그램을 동시에 사용할 수 있는 이유
하나의 프로세스 안에 여러 개의 스레드가 존재하는 것.
여러 개의 쓰레드가 같은 프로세스 내에서 자원을 공유하면서 작업하기 때문에 동기화( Synchronization ), 교착상태( deadlock )와 같은 문제가 발생할 확률이 높다.
스레드의 생성 방법은 Thread 클래스를 상속받아서 사용하는 방법과 Runnable 인터페이스를 구현하는 방법이다.
class BookStore extends Thread {
// 이 클래스에서 상위 클래스인 Thread의 run() 메소드를 재정의 해야 Thread의 실행부분을 작성할 수 있다.
@Override
public void run() {
super.run();
System.out.println("Book A");
}
}
public class Book {
public static void main(String[] args) {
BookStore bs = new BookStore();
bs.start();
}
}

// Runnable 인터페이스 상속받아 구현
class BookStore implements Runnable {
@Override
public void run() {
System.out.println("booooooooock");
}
}
public class Book {
public static void main(String[] args) {
/*
Runnable 인터페이스를 구현해 Thread를 만들 경우
객체 생성을 Thread 클래스 타입으로 만들어 줘야 한다.
run() 메소드의 트리거 역할을 하는 start()는 Thread 클래스에
정의되어있어서 객체 생성 시 반드시 Thread 타입으로 만들어야한다.
*/
BookStore bookStore = new BookStore();
bookStore.run();
Thread bs = new Thread(new BookStore());
bs.start();
}
}


class BookStore implements Runnable {
@Override
public void run() {
System.out.println("booooooooock");
}
}
public class Book {
public static void main(String[] args) {
Thread bs = new Thread(new BookStore());
System.out.println(bs.getState());
bs.start();
}
}

출력된 NEW : 호출 스택을 아직 생성하지 않아서 NEW의 결과 출력
class BookStore implements Runnable {
@Override
public void run() {
System.out.println("booooooooock");
}
}
public class Book {
public static void main(String[] args) {
Thread bs = new Thread(new BookStore());
System.out.println(bs.getState());
bs.start();
System.out.println(bs.getState());
}
}

출력된 RUNNABLE : start()를 호출한다고해서 바로 실행되는 것이 아니라 실행 대기열에 저장된 후 실행된다. 이 때 실행 대기중 상태이기때문에 RUNNABLE이 먼저 출력된다.
package org.example;
class BookStore implements Runnable {
@Override
public void run() {
exhibitBook();
}
public synchronized void exhibitBook() {
while (true) {
/*
책을 전시하고 있다는 가정을 두고
만약 새로운 책을 전시한다면 먼저 전시하던 책을 끝낼 때까지
새로운 책을 전시할 수 없다.
*/
}
}
}
public class Book {
public static void main(String[] args) throws InterruptedException {
Thread bs = new Thread(new BookStore());
Thread newBs = new Thread(new BookStore());
bs.start();
newBs.start();
Thread.sleep(1000);
System.out.println(bs.getState()); // Runnable
System.out.println(newBs.getState()); // Blocked
System.exit(0);
}
}
class BookStore implements Runnable {
@Override
public void run() {
try {
Thread.sleep(3000);
System.out.println("책이 많이 입고되었을 때");
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
입고에 대해 먼저 처리한다.
요청사항이 끝날 때까지 기존에 전시하던 책은 잠시 멈춘 상태로 기다린다.
*/
System.out.println("전시하던 책은 잠시 : " + WaitingState.exhibit.getState());
}
}
public class WaitingState implements Runnable {
public static Thread exhibit;
public static void main(String[] args) {
exhibit = new Thread(new WaitingState());
exhibit.start(); // 책을 전시해두기 시작한다.
}
@Override
public void run() {
Thread bookStore = new Thread(new BookStore()); // 전시 도중 책이 입고되었다.
bookStore.start(); // 입고된 책을 정리하기 시작한다.
try {
bookStore.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}

class FindBook implements Runnable {
@Override
public void run() {
try {
Thread.sleep(5000);
System.out.println("찾으시는 책 가져왔습니다.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
public class TimeWaitingState {
public static void main(String[] args) throws InterruptedException {
Thread findBook = new Thread(new FindBook());
findBook.start();
/*
책을 가지러가는 시간이 5초라고 하면 책을 찾는 손님은
"찾으시는 책 가져왔습니다" 라는 말을 기다리는 경우이다.
*/
Thread.sleep(1000);
System.out.println(findBook.getState());
}
}

public class TerminateState implements Runnable{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new TerminateState());
thread.start();
thread.sleep(1000);
System.out.println(thread.getState());
}
@Override
public void run() {
}
}

어떠한 작업을 다른 작업보다 먼저 처리되어야하는 경우가 존재할 것이다.
그럴때 우선순위를 설정하여 중요한 작업부터 실행될 수 있게끔 할 수 있다.
class BookType implements Runnable {
@Override
public void run() {
exhibitBook(Thread.currentThread().getName());
}
private void exhibitBook(String name) {
System.out.println(name + "가 전시되고 있습니다.");
}
}
public class BookPriority {
public static void main(String[] args) {
try {
Thread history = new Thread(new BookType(), "history");
Thread science = new Thread(new BookType(), "science");
Thread math = new Thread(new BookType(), "math");
/*
우선순위는 1~10까지 존재하며 설정하지 않은 경우에 기본은 5이다.
setPriority 메소드를 이용하여 우선순위를 바꿀 수 있다.
*/
history.setPriority(1);
science.setPriority(4);
math.setPriority(7);
history.start();
science.start();
math.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}

자바에서 메인 메소드를 통해 프로그램이 실행되면 하나의 스레드가 시작되는데, 이것을 메인 스레드라고 한다.
우리가 만든 프로그램을 실행하면 JVM에서 자동으로 메인 스레드를 생성해준다.
public class MainThread {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName()); // main
}
}
멀티스레드 프로그래밍의 특징은 하나의 프로세스에 여러 스레드가 접근한다는 의미이다.
하나의 데이터를 공유하여 작업해 더욱 빠른 동작을 할 수 있다.
반면에, 하나의 작업에 여러 스레드가 동시에 접근하여 똑같은 작업을 한다면 더 좋지 않을 수 있다.
그것을 방지하기 위해 동기화라는 개념이 들어왔다.
sychronized( 객체의 참조 변수 ) {
/*
참조 변수로 들어온 객체는 synchronized 블록에 들어오면 lock에 잠기고, 블록이 종료 되면 lock이 해제된다.
잠긴 사이에는 다른 스레드가 접근할 수 없다.
*/
}
public synchronized void exam() {
/* 한 스레드가 sychronized 메소드를 호출해서 수행하고 있으면
이 메소드가 종료될 때까지 다른 스레드는
이 메소드를 호출할 수 없다
*/
}
public class BookStore {
public static void main(String[] args) {
try {
ExhibitBook exhibit = new ExhibitBook(10);
new Thread(exhibit, "반지의 제왕").start();
new Thread(exhibit, "호빗").start();
new Thread(exhibit, "하얼빈").start();
new Thread(exhibit, "행복해라").start();
new Thread(exhibit, "잘될 수밖에 없는 너에게").start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class ExhibitBook implements Runnable {
private int bookCount;
private String[] bookshelf = {"_", "_", "_", "_"};
public ExhibitBook(int count) {
bookCount = count;
}
@Override
public void run() {
while (bookCount > 0) {
synchronized (this) { // 한번에 하나의 스레드만 접근
bookCount--;
System.out.println("전시중인 책의 개수 : " + bookCount);
}
for (int i = 0; i < bookshelf.length; i++) {
if (!bookshelf[i].equals("_")) {
continue;
}
synchronized (this) {
bookshelf[i] = Thread.currentThread().getName();
System.out.println(bookshelf[i] + "전시하는 중입니다.");
}
// 책을 전시하는 데 걸리는 시간은 5초로 가정
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " 전시를 마쳤습니다");
System.out.println("==============================");
bookshelf[i] = "_";
}
break;
}
// 새로운 책 전시를 위한 준비시간
try {
Thread.sleep(Math.round(1000 * Math.random()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}


둘 이상의 스레드가 lock을 획득하기 위해서 기다리는데, lock을 가진 스레드도 lock을 얻기위해 대기하며 모든 스레드가 블록 상태에 놓이는 것을 의미한다.
public class TestThread {
public static Object Lock1 = new Object();
public static Object Lock2 = new Object();
public static void main(String args[]) {
Thread1 t1 = new Thread1();
Thread2 t2 = new Thread2();
t1.start();
t2.start();
}
private static class Thread1 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 1: Holding lock 1...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
}
}
private static class Thread2 extends Thread {
public void run() {
synchronized (Lock2) {
System.out.println("Thread 2: Holding lock 2...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for lock 1...");
synchronized (Lock1) {
System.out.println("Thread 2: Holding lock 1 & 2...");
}
}
}
}
}