상단의 코드를 보면 threadLocal 이 다른 Thread 에 영향을 주지않는것을 볼 수 있다
PC(Programe Counter) Register
PC는 다음에 실행할 명령어의 주소를 가리키는 역할을 하므로, 스레드마다 PC의 값은 서로 다를 수있음
스레드는 자신만의 레지스터 세트를 가지며, 이를 통해 각각의 스레드는 자신의 데이터를 저장하고 연산을 수행합니다. 스레드 간에는 레지스터 값을 공유하지 않으며, 독립적으로 작업을 수행
마무리 정리
왜??? Thread 가 공유자원과 독립자원으로 나누어져있을까??
로컬 변수와 스레드 스택은 각각 스레드의 독립성을 보장하고, 동시에 실행되는 여러 스레드가 각자의 상태와 실행 흐름을 유지할 수 있도록 도와줍니다.
이러한 독립적인 자원을 가지는 것은 스레드 간의 상호작용과 동시성 문제를 다루는 데 유용합니다
Thread 는 공유자원과 독립자원으로 나누어져있다
Thread 의 공유자원은 Process 의 자원들을 공유한다.
독립자원은 각각의 Thread 가 개별적으로 가지고있는 자원이다.
Java 에서 Thread 를 사용한는 방법
java 에서 Thread 를 사용하는 방법은 대표적으로 3가지? 정도가 있는것같다
더있는지는 잘..모르겠슴다.. ㅎㅎ😄
1. Thread 상속
첫번쨰로 Thread 클래스를 extends 받아서 처리하는 방법이 있다
public class MyThread extends Thread {
public void run() {
// 스레드 동작을 구현
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
위처럼 Thread 를 상속받아서 MyThread 클래스는 run() 메서드를 오버라이딩하여 스레드의 동작을 구현하고, start() 메서드를 호출하여 스레드를 실행
Runnable 클래스 인터페이스를 구현한 클래스를 생성하여 동작을 정의하는 방법
public class MyRunnable implements Runnable {
public void run() {
//스레드 동작을 구현
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
위처럼 Runnable 을 구현하여 start 로 실행.
Lambda 표현식으로 Runnable 구현
public class LambdaThreadExample {
public static void main(String[] args) {
Runnable runnable = () -> {
// 스레드 동작을 구현
};
Thread thread1 = new Thread(runnable);
thread1.start();
Thread thread2 = new Thread(()->{
System.out.println(Thread.currentThread());
});
thread2.start();
}
}
람다식으로 Runnable 을구현하여 실행하거나 파라미터로 바로 넣어서 실행( 동작 파라미터 기억하죠?)
위의 3가지 예제를 볼때 궁금증이 생겨야합니다
왜 Thread run을 상속받았는데 다 thread run 을하지않고 start 를 했나
Runnable 과 Thread 를 상속받는거에 어떠한 차이점이 있나?
정도의 궁금증이 생겨나야합니다.
2. Thread. start() 와 Thread.run() 의 차이
결론부터 말씀드리면
<aside>
⚠️ start() 를 사용하면 새로운 Thread 가 생성되고 run() 이 실행된다.
run() 을 사용할경우 기존의 Thread 를 생성하지않는다
</aside>
로 알수있겠습니다. 코드를 볼까요??
start() 사용시
class MyThread extends Thread {
public void run()
{
System.out.println("Current thread name: "
+ Thread.currentThread().getName());
System.out.println("run() method called");
}
}
class Main {
public static void main(String[] args)
{
MyThread t = new MyThread();
t.start();
}
}
위와 같은 코드에서의 결과는
Current thread name: Thread-0
run() method called
이라는 결과가 나옵니다.
즉 Thread-0 이라는 Thread 가 생성이 된다음 run() 메서드를 호출합니다.
run() 사용시
class MyThread2 extends Thread {
public void run()
{
System.out.println("Current thread name: "
+ Thread.currentThread().getName());
System.out.println("run() method called");
}
}
class Main2 {
public static void main(String[] args)
{
MyThread t = new MyThread();
t.run();
}
}
위의 코드에서 결과는
Current thread name: main
run() method called
이라는 결과가 나오는데
즉 main Thread 를 실행시켰고 새로운 Thread 를 생성하지 않았다는 결과가 나오는겁니다.
따라서 멀티 스레딩이 발생하지 않았다는것을 알수있습니다.
start() 2번 호출시
class MyThread3 extends Thread {
public void run()
{
System.out.println("Current thread name: "
+ Thread.currentThread().getName());
System.out.println("run() method called");
}
}
class Main3 {
public static void main(String[] args)
{
MyThread3 t = new MyThread3();
t.start();
t.start();
}
}
위의 코드에서
Exception in thread "main" java.lang.IllegalThreadStateException
at java.base/java.lang.Thread.start(Thread.java:789)
at javastudy.personalstudy.thread.different.Main3.main(MyThread3.java:25)
Current thread name: Thread-0
run() method called
라는 결과가 나오는것을 알수있습니다.
이를 보면 start() 메서드는 2번호출 될수없다는것을 알수있습니다.
그렇기 때문에 start() 를 2번실행했을때 IllegalThreadStateException 가 발생한것을 알수있습니다.
run() 2번 호출시
class MyThread4 extends Thread {
public void run()
{
System.out.println("Current thread name: "
+ Thread.currentThread().getName());
System.out.println("run() method called");
}
}
class Main4 {
public static void main(String[] args)
{
MyThread t = new MyThread();
t.run();
t.run();
}
}
위의 코드에서
Current thread name: main
run() method called
Current thread name: main
run() method called
이라는 결과가 나오는것을 알수있습니다. run() 메서드를 두 번 호출해도 예외가 발생하지 않고 예상대로 두 번 실행되지만 메인 스레드 자체에서 실행되는것을 알수있습니다.
요약 정리
| start() | run()
-- | -- | --
Thread 생성 | 새로운 Thread 생성 | 새로운 Thread 를 생성하지 않는다.
run() 을 호출한놈의 Thread 를 씀 | |
호출 횟수 | 한번 이상의 호출할수 없다 | 여러번 호출가능
구현 위치 | Thread 에 정의되어있다 | Runnable 인터페이스에서 있는 메서드를 재정의 해야한다.
사용할때 | 멀티스래딩, 비동기처리 | 단일스레드, 테스트 및 디버깅
start() 가 좋냐? run() 이 좋냐? 그런거없슴다.
그냥 언제 어떻게 쓸지에따라서 다르게 쓰이는겁니다.
일단 뮤텍스는 상호배제를 위한 기법인데 하나의 Thread 만 임계영역 critical section 에 진입할수 있도록 도와줍니다. 하단의 코드를 보면서 이해를 해봅시다.
코드
package javastudy.personalstudy.thread.syncronizedways;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MutexExample {
private static int count = 0;
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
// 앞서 설명했듯이 thread 에서 runnable 을 lambda 로 구현
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
lock.lock(); //<-- 해당자원이 임계영역에 진입하기전 lock 을검
try {
count++;
} finally {
lock.unlock(); // <-- 다 끝났을 경우 unlock 으로 해제
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + count);
}
}
Semaphore
2가지 연산으로 구성 P (Wait) 연산과 V (Signal) 연산으로 구성
카운터 값이 음수가 될 수 없고, 0 이상의 정수
즉 P (Wait) 연산은 세마포어 카운터를 1 감소시키고, 만약 카운터가 0 이하가 되면 호출한 스레드는 블록됩니다.
V (Signal) 연산은 세마포어 카운터를 1 증가시키고, 만약 카운터가 0 이상이 되면 대기 중인 스레드 중 하나를 깨웁니다.
스레드 간의 안전한 공유 자원 액세스, 생산자-소비자 문제, 우선순위 스케줄링, 임계 영역 제한 등 다양한 상황에서 유용하게 활용
위에 말이 무슨말이야???? 괜찮습니다 다~~ 예제로 이해가 됩니다잉
코드
package javastudy.personalstudy.thread.syncronizedways.semaphore;
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static int count = 0;
private static Semaphore semaphore = new Semaphore(1); // 동시에 접근할수 있는 Thread 개수를 1개로 선정한것
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
try {
semaphore.acquire(); // Semaphore 의 값을 1감소 처음에 1할당한것을 감소시키니까 0 이겠죠?
for (int i = 0; i < 1000; i++) {
count++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 종료됬으니까 Semaphore 의 cnt 값을 증가
}
});
Thread thread2 = new Thread(() -> {
try {
semaphore.acquire();
for (int i = 0; i < 1000; i++) {
count++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + count);
}
}
Monitor
하나의 객체(데이터) 마다 하나의 모니터가 될수있고 모니터는 객체(데이터) 가 두개이상의 접근할 수 없도록 막는 lock 을 제공하여 동기화를 하는 기법
쉽게 생각하면 데이터(객체)를 모니터링 할수 있는 기법 이라고 볼수있다
코드
package javastudy.personalstudy.thread.syncronizedways.monitor;
public class ThreadMonitorExample {
private static intcount= 0;
public static void main(String[] args) {
//주요 스레드 생성
Thread mainThread = new Thread(() -> {
System.out.println("주요 스레드 시작");
//보조 스레드 생성
for (int i = 1; i <= 5; i++) {
Thread subThread = new Thread(() -> {
for (int j = 0; j < 400; j++) {
incrementCount();
}
System.out.println("보조 스레드 " + Thread.currentThread().getId() + " 종료");
System.out.println("보조 스레드 상태 : " + Thread.currentThread().getState());
});
subThread.start();
}
//보조 스레드들이 모두 종료될 때까지 대기
while (count< 2000) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("주요 스레드 종료");
});
//주요 스레드 시작
mainThread.start();
//주요 스레드와 보조 스레드 모니터링
while (mainThread.isAlive()) {
System.out.println("주요 스레드 상태: " + mainThread.getState());
System.out.println("현재 count 값: " +count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
System.out.println("프로그램 종료");
mainThread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
System.out.println("주요 스레드 상태 : "+ mainThread.getState());
}
}
private static synchronized void incrementCount() {
count++;
}
}
PS : 현재 Java 에서는 직접적인 동기화 기법인 synchronized 를 지양하고
Atomic Variable 혹은 conurrent Collection Framework 등을 알아 보면 좋을것같습니다.
## Thread 란 ??
> 일반적으로 Thread 를 알기위해서는 Process 를 알아야합니다.
그럼 우리가 생각하는 Process 란 뭘까요???
자 그럼 위의 Process 가 하나의 Thread 로 이루어진것을 알았으니 다음을 확인해봅시다.
Process 는 **최소하나의 Task(작업)** 으로 이루어져있습니다.
그럼 이런 **작업을 실행하는 주체를 Thread** 라고합니다.
모르는 내용이너무 많지않나요??? 조금만더 정리해봅시다
**앞서 Process 가 뭐라고했는지 기억하죠??**
→ 그럼 Thread 의 자원은 누가 할당해줄까요??? Process 에는 어떠한 정보들이 들어있을까요??
### 1. Process 의 자원할당
**자원할당**
- Process 는 운영체제에 의해 CPU에 할당되어 실행됩니다
—> 어떤방식으로???? 방식이 정해져있나???
- 위와 같은 방식들을 처리해주는게 Scheduler 입니다.
`~~이 부분을 이야기하면 너무 OS 쪽으로 깊게 해야해서 일단 배제하겠습니다.~~`
—> 그럼 Process 의 내부 자원은 어떻게 구성되어 있나??
Process 에 있는 데이터 정보들은
1. Stack
- 함수 호출, 지역 변수, 매개 변수 등을 관리하기 위한 함수 호출 및 복귀 주소, 지역 변수 등을 저장
2. Heap
- 프로세스가 실행 중에 동적으로 메모리를 할당하고 해제하는 데 사용(우리가 생각하는 객체)
3. Data
- 프로세스는 프로그램의 전역 변수, 정적 변수, 상수 등의 데이터를 포함 (세그먼트에 저장)
4. Text(Code)
- 우리가 작성한 code 들이 기입(Read-only 로 구성)
5. Register(정확히는 CPU 에 포함되어있다)
- 프로세스는 CPU 내부의 레지스터를 사용하여 데이터를 저장하고 처리합니다. 이는 연산에 필요한 피연산자들이 저장되고, 연산 결과도 임시로 저장
6. PCB(Process Control Block)
- 프로세스 식별자(PID) : 프로세스의 PK 라고 보면됩니다
- 프로세스 상태(Process Status) : 프로세스의 **현재상태** 표시
- 프로그램 카운터(Programe Counter) : 프로세스가 다음 실행할 명령어 주소를 가르키는 레지스터의 값
- 레지스터 값: 프로세스의 레지스터 상태를 저장
- 스케줄링 정보: 프로세스의 우선순위, 스케줄링 알고리즘에 필요한 정보
- 할당된 자원 정보: 프로세스가 할당 받은 메모리, 입출력 장치, 파일 등의 자원 정보를 저장
- 프로세스 계정 정보: 프로세스의 사용 시간, CPU 시간, 생성 시간 등의 계정 정보를 포함
**마무리 정리**
1. Process 는 프로그램이 실행되는것을 Process 라고하고 좀더 명확하게 표현하면 Programe 의 instance 를 Process 고도 할 수있겠다
2. Process 는 Register, Stack , Heap 등의 여러가지 구조로 이루어져있다
3. 프로세스의 정보들은 PCB에 할당되어있다.
### 2. Thread 의 자원할당
> 자 이제 맨처음 나왔던 그림을 다시 보자 Thread 부분 말고는 왜 저런 그림으로 되어있는지 알수있을것이다.
>
>
> 그럼 이제 Thread 에 대해서 알아보자
>
- **Thread 란 뭐라고?(간략하게)**
프로세스의 최소단위의 작업(Task)를 실행하는것을 Thread 라고한다.
**자원할당**
Thread 는 앞서 말한것처럼 Process 와 관련이있기 때문에
**공유자원과 각각 독립적인 자원이 존재할수 있슴다.**
Thread 이미지 (왼쪽이 single 오른쪽이 mulitThread)
- **공유자원**
1. 메모리
- 프로세스의 메모리 공간을 공유합니다. 따라서, 스레드는 동일한 코드, 데이터, 힙을 공유하며, 이를 통해 변수, 배열, 객체 등의 데이터를 공유
2. 파일 디스크립터
- 여러 스레드는 동시에 파일에 대한 I/O 작업을 수행가능, 컴퓨터 시스템에서 파일이나 I/O 장치를 식별하고 조작하기 위해 사용되는 정수 값이라고 봐도 무방하다
3. 시그널 핸들러
- 프로세스의 시그널 핸들러를 공유합니다. 시그널은 프로세스에게 발생한 이벤트를 알리는 메커니즘이며, 스레드는 이를 처리하기 위해 동일한 시그널 핸들러를 사용가능
4. 열린 소캣
- 스레드는 프로세스의 열린 소켓을 공유합니다. 이는 네트워크 통신 작업을 스레드 간에 공유가능
- **독립자원**
1. **Thread Local 변수(이놈은 저희가 나중에 직접쓸수도 있습니다)**
- Thread Local은 Java에서 스레드 간에 데이터를 공유하지 않고, 각 스레드별로 독립적으로 유지하고자 할 때 사용되는 기능이고
- Thread Local은 각 스레드마다 별도의 변수 인스턴스를 제공하며, 해당 변수는 스레드 내에서만 접근할 수 있습니다.
- 각 스레드는 Thread Local 변수에 값을 설정하고 가져오는 메서드를 사용하여 독립적인 상태를 유지할 수 있습니다.
- 예를 들어, 스레드 간에 로깅 정보, 트랜잭션 컨텍스트, **사용자 인증 정보** 등을 공유하지 않고 스레드 내에서만 유지하고자 할 때 Thread Local을 사용할 수 있습니다
- **이때 Thread Local 사용시 remove 를 하지 않는다면 어떻게 될까??**
- **코드**
```java
public class ThreadLocalExample {
private static ThreadLocal threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 스레드 1
Thread thread1 = new Thread(() -> {
threadLocal.set(1);
try {
TimeUnit.SECONDS.sleep(2); // 2초 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: " + threadLocal.get()); // 1 출력
});
// 스레드 2
Thread thread2 = new Thread(() -> {
threadLocal.set(2);
System.out.println("Thread 2: " + threadLocal.get()); // 2 출력
});
// 스레드 3
Thread thread3 = new Thread(() -> {
threadLocal.set(3);
System.out.println("Thread 3: " + threadLocal.get());
});
// 각 스레드 시작
thread1.run();
thread2.start();
thread3.start();
try {
thread1.join();
thread2.join();
thread3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 메인 스레드에서의 값 출력
System.out.println("Main Thread: " + threadLocal.get()); // 1 출력
// thread1.run(); // remove 해야하는 예시
//
}
}
```
2. Thread Stack
- Java에서 각 스레드는 독립적인 실행 흐름을 위해 별도의 스택(Stack)을 가지고 있습니다.
- 스레드 스택은 함수 호출과 관련된 정보, 지역 변수, 매개 변수 등을 저장하는 데 사용됩니다.
- 각 스레드의 스택은 스레드의 실행 흐름을 관리하고, 스레드마다 독립적인 함수 호출과 지역 변수의 스코프를 지원합니다.
- 스레드 스택은 자동으로 생성되고 관리되며, 스레드의 수명 주기와 함께 할당 및 해제됩니다.
- 스레드 스택은 JVM에 의해 관리되며, 스택 오버플로우와 같은 문제가 발생할 수 있습니다. JVM은 스택 오버플로우를 감지하고 예외를 발생시켜 프로그램의 안정성을 유지합니다.
- **코드**
```java
import java.util.concurrent.TimeUnit;
public class ThreadLocalExample {
private static ThreadLocalthreadLocal= new ThreadLocal<>();
public static void main(String[] args) {
//스레드 1
Thread thread1 = new Thread(() -> {
threadLocal.set(1);
try {
TimeUnit.SECONDS.sleep(2);// 2초 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: " +threadLocal.get());// 1출력
});
//스레드 2
Thread thread2 = new Thread(() -> {
threadLocal.set(2);
System.out.println("Thread 2: " +threadLocal.get());// 2출력
});
//스레드 3
Thread thread3 = new Thread(() -> {
threadLocal.set(3);
System.out.println("Thread 3: " +threadLocal.get());
});
//각 스레드 시작
thread1.start();
thread2.start();
thread3.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//메인 스레드에서의 값 출력
System.out.println("Main Thread: " +threadLocal.get());// null출력
}
}
```
상단의 코드를 보면 threadLocal 이 다른 Thread 에 영향을 주지않는것을 볼 수 있다
3. PC(Programe Counter) Register
- PC는 다음에 실행할 명령어의 주소를 가리키는 역할을 하므로, 스레드마다 PC의 값은 서로 다를 수있음
- 스레드는 자신만의 레지스터 세트를 가지며, 이를 통해 각각의 스레드는 자신의 데이터를 저장하고 연산을 수행합니다. 스레드 간에는 레지스터 값을 공유하지 않으며, 독립적으로 작업을 수행
**마무리 정리**
- 왜??? Thread 가 공유자원과 독립자원으로 나누어져있을까??
로컬 변수와 스레드 스택은 각각 스레드의 독립성을 보장하고, 동시에 실행되는 여러 스레드가 각자의 상태와 실행 흐름을 유지할 수 있도록 도와줍니다.
이러한 독립적인 자원을 가지는 것은 스레드 간의 **상호작용과 동시성 문제**를 다루는 데 유용합니다
- Thread 는 공유자원과 독립자원으로 나누어져있다
- Thread 의 공유자원은 Process 의 자원들을 공유한다.
독립자원은 각각의 Thread 가 개별적으로 가지고있는 자원이다.
---
## Java 에서 Thread 를 사용한는 방법
> java 에서 Thread 를 사용하는 방법은 대표적으로 3가지? 정도가 있는것같다
더있는지는 잘..모르겠슴다.. ㅎㅎ😄
>
### 1. Thread 상속
1. 첫번쨰로 Thread 클래스를 extends 받아서 처리하는 방법이 있다
```java
public class MyThread extends Thread {
public void run() {
// 스레드 동작을 구현
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
```
위처럼 Thread 를 상속받아서 MyThread 클래스는 run() 메서드를 오버라이딩하여 스레드의 동작을 구현하고, start() 메서드를 호출하여 스레드를 실행
1. Runnable 클래스 인터페이스를 구현한 클래스를 생성하여 동작을 정의하는 방법
```java
public class MyRunnable implements Runnable {
public void run() {
//스레드 동작을 구현
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
```
위처럼 Runnable 을 구현하여 start 로 실행.
2. Lambda 표현식으로 Runnable 구현
```java
public class LambdaThreadExample {
public static void main(String[] args) {
Runnable runnable = () -> {
// 스레드 동작을 구현
};
Thread thread1 = new Thread(runnable);
thread1.start();
Thread thread2 = new Thread(()->{
System.out.println(Thread.currentThread());
});
thread2.start();
}
}
```
람다식으로 Runnable 을구현하여 실행하거나 파라미터로 바로 넣어서 실행( 동작 파라미터 기억하죠?)
**위의 3가지 예제를 볼때 궁금증이 생겨야합니다**
1. 왜 Thread run을 상속받았는데 다 thread run 을하지않고 start 를 했나
2. Runnable 과 Thread 를 상속받는거에 어떠한 차이점이 있나?
정도의 궁금증이 생겨나야합니다.
### 2. Thread. start() 와 Thread.run() 의 차이
결론부터 말씀드리면
로 알수있겠습니다. 코드를 볼까요??
1. start() 사용시
```java
class MyThread extends Thread {
public void run()
{
System.out.println("Current thread name: "
+ Thread.currentThread().getName());
System.out.println("run() method called");
}
}
class Main {
public static void main(String[] args)
{
MyThread t = new MyThread();
t.start();
}
}
```
위와 같은 코드에서의 결과는
```java
Current thread name: Thread-0
run() method called
```
이라는 결과가 나옵니다.
즉 **Thread-0 이라는 Thread 가 생성**이 된다음 **run() 메서드를 호출**합니다.
2. run() 사용시
```java
class MyThread2 extends Thread {
public void run()
{
System.out.println("Current thread name: "
+ Thread.currentThread().getName());
System.out.println("run() method called");
}
}
class Main2 {
public static void main(String[] args)
{
MyThread t = new MyThread();
t.run();
}
}
```
위의 코드에서 결과는
```java
Current thread name: main
run() method called
```
이라는 결과가 나오는데
즉 main Thread 를 실행시켰고 새로운 Thread 를 생성하지 않았다는 결과가 나오는겁니다.
따라서 **멀티 스레딩이 발생하지 않았다**는것을 알수있습니다.
3. start() 2번 호출시
```java
class MyThread3 extends Thread {
public void run()
{
System.out.println("Current thread name: "
+ Thread.currentThread().getName());
System.out.println("run() method called");
}
}
class Main3 {
public static void main(String[] args)
{
MyThread3 t = new MyThread3();
t.start();
t.start();
}
}
```
위의 코드에서
```java
Exception in thread "main" java.lang.IllegalThreadStateException
at java.base/java.lang.Thread.start(Thread.java:789)
at javastudy.personalstudy.thread.different.Main3.main(MyThread3.java:25)
Current thread name: Thread-0
run() method called
```
라는 결과가 나오는것을 알수있습니다.
이를 보면 start() 메서드는 2번호출 될수없다는것을 알수있습니다.
그렇기 때문에 start() 를 2번실행했을때 `IllegalThreadStateException` 가 발생한것을 알수있습니다.
4. run() 2번 호출시
```java
class MyThread4 extends Thread {
public void run()
{
System.out.println("Current thread name: "
+ Thread.currentThread().getName());
System.out.println("run() method called");
}
}
class Main4 {
public static void main(String[] args)
{
MyThread t = new MyThread();
t.run();
t.run();
}
}
```
위의 코드에서
```java
Current thread name: main
run() method called
Current thread name: main
run() method called
```
이라는 결과가 나오는것을 알수있습니다. *`run()`* 메서드를 두 번 호출해도 예외가 발생하지 않고 예상대로 두 번 실행되지만 *메인* 스레드 자체에서 실행되는것을 알수있습니다.
**요약 정리**
| | start() | run() |
| --- | --- | --- |
| Thread 생성 | 새로운 Thread 생성 | 새로운 Thread 를 생성하지 않는다.
run() 을 호출한놈의 Thread 를 씀 |
| 호출 횟수 | 한번 이상의 호출할수 없다 | 여러번 호출가능 |
| 구현 위치 | Thread 에 정의되어있다 | Runnable 인터페이스에서 있는 메서드를 재정의 해야한다. |
| 사용할때 | 멀티스래딩, 비동기처리 | 단일스레드, 테스트 및 디버깅 |
start() 가 좋냐? run() 이 좋냐? 그런거없슴다.
그냥 언제 어떻게 쓸지에따라서 다르게 쓰이는겁니다.
---
## Mulit Thread . . .
> 멀티스레드란?
하나의 프로그램에 동시에 여러개의 일을 수행할수 있도록 해주는 것이라고 볼수있다
>
자 사실 멀티스레드의 개념은 생각보다 단순하다.
여러개의 Thread 가 하나의 Task 를 처리한다고 한다면 멀티스레딩이다.
사실 여기서 가장 중요한건 **동기화 기법** 을 어떻게 처리할지 이다.
### Mulit Thread 동기화 기법..
1. Mutex
- 일단 뮤텍스는 상호배제를 위한 기법인데 하나의 Thread 만 임계영역 critical section 에 진입할수 있도록 도와줍니다. 하단의 코드를 보면서 이해를 해봅시다.
- 코드
```java
package javastudy.personalstudy.thread.syncronizedways;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MutexExample {
private static int count = 0;
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
// 앞서 설명했듯이 thread 에서 runnable 을 lambda 로 구현
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
lock.lock(); //<-- 해당자원이 임계영역에 진입하기전 lock 을검
try {
count++;
} finally {
lock.unlock(); // <-- 다 끝났을 경우 unlock 으로 해제
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + count);
}
}
```
2. Semaphore
- 2가지 연산으로 구성 P (Wait) 연산과 V (Signal) 연산으로 구성
- 카운터 값이 음수가 될 수 없고, 0 이상의 정수
- 즉 P (Wait) 연산은 세마포어 카운터를 1 감소시키고, 만약 카운터가 0 이하가 되면 호출한 스레드는 블록됩니다.
V (Signal) 연산은 세마포어 카운터를 1 증가시키고, 만약 카운터가 0 이상이 되면 대기 중인 스레드 중 하나를 깨웁니다.
- 스레드 간의 안전한 공유 자원 액세스, 생산자-소비자 문제, 우선순위 스케줄링, 임계 영역 제한 등 다양한 상황에서 유용하게 활용
**위에 말이 무슨말이야???? 괜찮습니다 다~~ 예제로 이해가 됩니다잉**
- 코드
```java
package javastudy.personalstudy.thread.syncronizedways.semaphore;
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static int count = 0;
private static Semaphore semaphore = new Semaphore(1); // 동시에 접근할수 있는 Thread 개수를 1개로 선정한것
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
try {
semaphore.acquire(); // Semaphore 의 값을 1감소 처음에 1할당한것을 감소시키니까 0 이겠죠?
for (int i = 0; i < 1000; i++) {
count++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 종료됬으니까 Semaphore 의 cnt 값을 증가
}
});
Thread thread2 = new Thread(() -> {
try {
semaphore.acquire();
for (int i = 0; i < 1000; i++) {
count++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + count);
}
}
```
3. Monitor
- 하나의 객체(데이터) 마다 하나의 모니터가 될수있고 모니터는 객체(데이터) 가 두개이상의 접근할 수 없도록 막는 lock 을 제공하여 동기화를 하는 기법
- 쉽게 생각하면 **데이터(객체)를 모니터링 할수 있는 기법** 이라고 볼수있다
- 코드
```java
package javastudy.personalstudy.thread.syncronizedways.monitor;
public class ThreadMonitorExample {
private static intcount= 0;
public static void main(String[] args) {
//주요 스레드 생성
Thread mainThread = new Thread(() -> {
System.out.println("주요 스레드 시작");
//보조 스레드 생성
for (int i = 1; i <= 5; i++) {
Thread subThread = new Thread(() -> {
for (int j = 0; j < 400; j++) {
incrementCount();
}
System.out.println("보조 스레드 " + Thread.currentThread().getId() + " 종료");
System.out.println("보조 스레드 상태 : " + Thread.currentThread().getState());
});
subThread.start();
}
//보조 스레드들이 모두 종료될 때까지 대기
while (count< 2000) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("주요 스레드 종료");
});
//주요 스레드 시작
mainThread.start();
//주요 스레드와 보조 스레드 모니터링
while (mainThread.isAlive()) {
System.out.println("주요 스레드 상태: " + mainThread.getState());
System.out.println("현재 count 값: " +count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
System.out.println("프로그램 종료");
mainThread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
System.out.println("주요 스레드 상태 : "+ mainThread.getState());
}
}
private static synchronized void incrementCount() {
count++;
}
}
```
**PS : 현재 Java 에서는 직접적인 동기화 기법인 synchronized 를 지양하고
Atomic Variable 혹은 conurrent Collection Framework 등을 알아 보면 좋을것같습니다.**
Discussed in https://github.com/FeGwan-Training/FeGwan/discussions/12
Thread 란 ??
자 그럼 위의 Process 가 하나의 Thread 로 이루어진것을 알았으니 다음을 확인해봅시다.
Process 는 최소하나의 Task(작업) 으로 이루어져있습니다.
그럼 이런 작업을 실행하는 주체를 Thread 라고합니다.
모르는 내용이너무 많지않나요??? 조금만더 정리해봅시다
앞서 Process 가 뭐라고했는지 기억하죠??
→ 그럼 Thread 의 자원은 누가 할당해줄까요??? Process 에는 어떠한 정보들이 들어있을까요??
1. Process 의 자원할당
자원할당
Process 는 운영체제에 의해 CPU에 할당되어 실행됩니다
—> 어떤방식으로???? 방식이 정해져있나???
위와 같은 방식들을 처리해주는게 Scheduler 입니다.
~~이 부분을 이야기하면 너무 OS 쪽으로 깊게 해야해서 일단 배제하겠습니다.~~
—> 그럼 Process 의 내부 자원은 어떻게 구성되어 있나??
Process 에 있는 데이터 정보들은
프로세스 식별자(PID) : 프로세스의 PK 라고 보면됩니다
프로세스 상태(Process Status) : 프로세스의 현재상태 표시
프로그램 카운터(Programe Counter) : 프로세스가 다음 실행할 명령어 주소를 가르키는 레지스터의 값
레지스터 값: 프로세스의 레지스터 상태를 저장
스케줄링 정보: 프로세스의 우선순위, 스케줄링 알고리즘에 필요한 정보
할당된 자원 정보: 프로세스가 할당 받은 메모리, 입출력 장치, 파일 등의 자원 정보를 저장
프로세스 계정 정보: 프로세스의 사용 시간, CPU 시간, 생성 시간 등의 계정 정보를 포함
마무리 정리
2. Thread 의 자원할당
자 이제 맨처음 나왔던 그림을 다시 보자 Thread 부분 말고는 왜 저런 그림으로 되어있는지 알수있을것이다.
그럼 이제 Thread 에 대해서 알아보자
Thread 란 뭐라고?(간략하게)
프로세스의 최소단위의 작업(Task)를 실행하는것을 Thread 라고한다.
자원할당
Thread 는 앞서 말한것처럼 Process 와 관련이있기 때문에
공유자원과 각각 독립적인 자원이 존재할수 있슴다.
Thread 이미지 (왼쪽이 single 오른쪽이 mulitThread)
Thread Local 변수(이놈은 저희가 나중에 직접쓸수도 있습니다)
Thread Local은 Java에서 스레드 간에 데이터를 공유하지 않고, 각 스레드별로 독립적으로 유지하고자 할 때 사용되는 기능이고
Thread Local은 각 스레드마다 별도의 변수 인스턴스를 제공하며, 해당 변수는 스레드 내에서만 접근할 수 있습니다.
각 스레드는 Thread Local 변수에 값을 설정하고 가져오는 메서드를 사용하여 독립적인 상태를 유지할 수 있습니다.
예를 들어, 스레드 간에 로깅 정보, 트랜잭션 컨텍스트, 사용자 인증 정보 등을 공유하지 않고 스레드 내에서만 유지하고자 할 때 Thread Local을 사용할 수 있습니다
이때 Thread Local 사용시 remove 를 하지 않는다면 어떻게 될까??
코드
Thread Stack
Java에서 각 스레드는 독립적인 실행 흐름을 위해 별도의 스택(Stack)을 가지고 있습니다.
스레드 스택은 함수 호출과 관련된 정보, 지역 변수, 매개 변수 등을 저장하는 데 사용됩니다.
각 스레드의 스택은 스레드의 실행 흐름을 관리하고, 스레드마다 독립적인 함수 호출과 지역 변수의 스코프를 지원합니다.
스레드 스택은 자동으로 생성되고 관리되며, 스레드의 수명 주기와 함께 할당 및 해제됩니다.
스레드 스택은 JVM에 의해 관리되며, 스택 오버플로우와 같은 문제가 발생할 수 있습니다. JVM은 스택 오버플로우를 감지하고 예외를 발생시켜 프로그램의 안정성을 유지합니다.
코드
상단의 코드를 보면 threadLocal 이 다른 Thread 에 영향을 주지않는것을 볼 수 있다
PC(Programe Counter) Register
마무리 정리
왜??? Thread 가 공유자원과 독립자원으로 나누어져있을까??
로컬 변수와 스레드 스택은 각각 스레드의 독립성을 보장하고, 동시에 실행되는 여러 스레드가 각자의 상태와 실행 흐름을 유지할 수 있도록 도와줍니다.
이러한 독립적인 자원을 가지는 것은 스레드 간의 상호작용과 동시성 문제를 다루는 데 유용합니다
Thread 는 공유자원과 독립자원으로 나누어져있다
Thread 의 공유자원은 Process 의 자원들을 공유한다. 독립자원은 각각의 Thread 가 개별적으로 가지고있는 자원이다.
Java 에서 Thread 를 사용한는 방법
더있는지는 잘..모르겠슴다.. ㅎㅎ😄
1. Thread 상속
첫번쨰로 Thread 클래스를 extends 받아서 처리하는 방법이 있다
위처럼 Thread 를 상속받아서 MyThread 클래스는 run() 메서드를 오버라이딩하여 스레드의 동작을 구현하고, start() 메서드를 호출하여 스레드를 실행
Runnable 클래스 인터페이스를 구현한 클래스를 생성하여 동작을 정의하는 방법
위처럼 Runnable 을 구현하여 start 로 실행.
Lambda 표현식으로 Runnable 구현
람다식으로 Runnable 을구현하여 실행하거나 파라미터로 바로 넣어서 실행( 동작 파라미터 기억하죠?)
위의 3가지 예제를 볼때 궁금증이 생겨야합니다
정도의 궁금증이 생겨나야합니다.
2. Thread. start() 와 Thread.run() 의 차이
결론부터 말씀드리면
<aside> ⚠️ start() 를 사용하면 새로운 Thread 가 생성되고 run() 이 실행된다. run() 을 사용할경우 기존의 Thread 를 생성하지않는다
</aside>
로 알수있겠습니다. 코드를 볼까요??
start() 사용시
위와 같은 코드에서의 결과는
이라는 결과가 나옵니다. 즉 Thread-0 이라는 Thread 가 생성이 된다음 run() 메서드를 호출합니다.
run() 사용시
위의 코드에서 결과는
이라는 결과가 나오는데
즉 main Thread 를 실행시켰고 새로운 Thread 를 생성하지 않았다는 결과가 나오는겁니다. 따라서 멀티 스레딩이 발생하지 않았다는것을 알수있습니다.
start() 2번 호출시
위의 코드에서
라는 결과가 나오는것을 알수있습니다. 이를 보면 start() 메서드는 2번호출 될수없다는것을 알수있습니다. 그렇기 때문에 start() 를 2번실행했을때
IllegalThreadStateException
가 발생한것을 알수있습니다.run() 2번 호출시
위의 코드에서
이라는 결과가 나오는것을 알수있습니다.
run()
메서드를 두 번 호출해도 예외가 발생하지 않고 예상대로 두 번 실행되지만 메인 스레드 자체에서 실행되는것을 알수있습니다.요약 정리
| start() | run() -- | -- | -- Thread 생성 | 새로운 Thread 생성 | 새로운 Thread 를 생성하지 않는다. run() 을 호출한놈의 Thread 를 씀 | | 호출 횟수 | 한번 이상의 호출할수 없다 | 여러번 호출가능 구현 위치 | Thread 에 정의되어있다 | Runnable 인터페이스에서 있는 메서드를 재정의 해야한다. 사용할때 | 멀티스래딩, 비동기처리 | 단일스레드, 테스트 및 디버깅start() 가 좋냐? run() 이 좋냐? 그런거없슴다. 그냥 언제 어떻게 쓸지에따라서 다르게 쓰이는겁니다.
Mulit Thread . . .
하나의 프로그램에 동시에 여러개의 일을 수행할수 있도록 해주는 것이라고 볼수있다
상단의 Thread 이미지를 다시 보고오자…
자 사실 멀티스레드의 개념은 생각보다 단순하다.
여러개의 Thread 가 하나의 Task 를 처리한다고 한다면 멀티스레딩이다.
사실 여기서 가장 중요한건 동기화 기법 을 어떻게 처리할지 이다.
Mulit Thread 동기화 기법..
Mutex
일단 뮤텍스는 상호배제를 위한 기법인데 하나의 Thread 만 임계영역 critical section 에 진입할수 있도록 도와줍니다. 하단의 코드를 보면서 이해를 해봅시다.
코드
Semaphore
2가지 연산으로 구성 P (Wait) 연산과 V (Signal) 연산으로 구성
카운터 값이 음수가 될 수 없고, 0 이상의 정수
즉 P (Wait) 연산은 세마포어 카운터를 1 감소시키고, 만약 카운터가 0 이하가 되면 호출한 스레드는 블록됩니다.
V (Signal) 연산은 세마포어 카운터를 1 증가시키고, 만약 카운터가 0 이상이 되면 대기 중인 스레드 중 하나를 깨웁니다.
스레드 간의 안전한 공유 자원 액세스, 생산자-소비자 문제, 우선순위 스케줄링, 임계 영역 제한 등 다양한 상황에서 유용하게 활용 위에 말이 무슨말이야???? 괜찮습니다 다~~ 예제로 이해가 됩니다잉
코드
Monitor
하나의 객체(데이터) 마다 하나의 모니터가 될수있고 모니터는 객체(데이터) 가 두개이상의 접근할 수 없도록 막는 lock 을 제공하여 동기화를 하는 기법
쉽게 생각하면 데이터(객체)를 모니터링 할수 있는 기법 이라고 볼수있다
코드
PS : 현재 Java 에서는 직접적인 동기화 기법인 synchronized 를 지양하고 Atomic Variable 혹은 conurrent Collection Framework 등을 알아 보면 좋을것같습니다.
## Thread 란 ?? > 일반적으로 Thread 를 알기위해서는 Process 를 알아야합니다. 그럼 우리가 생각하는 Process 란 뭘까요??? 자 그럼 위의 Process 가 하나의 Thread 로 이루어진것을 알았으니 다음을 확인해봅시다. Process 는 **최소하나의 Task(작업)** 으로 이루어져있습니다. 그럼 이런 **작업을 실행하는 주체를 Thread** 라고합니다. 모르는 내용이너무 많지않나요??? 조금만더 정리해봅시다 **앞서 Process 가 뭐라고했는지 기억하죠??** → 그럼 Thread 의 자원은 누가 할당해줄까요??? Process 에는 어떠한 정보들이 들어있을까요?? ### 1. Process 의 자원할당 **자원할당** - Process 는 운영체제에 의해 CPU에 할당되어 실행됩니다 —> 어떤방식으로???? 방식이 정해져있나??? - 위와 같은 방식들을 처리해주는게 Scheduler 입니다. `~~이 부분을 이야기하면 너무 OS 쪽으로 깊게 해야해서 일단 배제하겠습니다.~~` —> 그럼 Process 의 내부 자원은 어떻게 구성되어 있나?? Process 에 있는 데이터 정보들은 1. Stack - 함수 호출, 지역 변수, 매개 변수 등을 관리하기 위한 함수 호출 및 복귀 주소, 지역 변수 등을 저장 2. Heap - 프로세스가 실행 중에 동적으로 메모리를 할당하고 해제하는 데 사용(우리가 생각하는 객체) 3. Data - 프로세스는 프로그램의 전역 변수, 정적 변수, 상수 등의 데이터를 포함 (세그먼트에 저장) 4. Text(Code) - 우리가 작성한 code 들이 기입(Read-only 로 구성) 5. Register(정확히는 CPU 에 포함되어있다) - 프로세스는 CPU 내부의 레지스터를 사용하여 데이터를 저장하고 처리합니다. 이는 연산에 필요한 피연산자들이 저장되고, 연산 결과도 임시로 저장 6. PCB(Process Control Block) - 프로세스 식별자(PID) : 프로세스의 PK 라고 보면됩니다 - 프로세스 상태(Process Status) : 프로세스의 **현재상태** 표시 - 프로그램 카운터(Programe Counter) : 프로세스가 다음 실행할 명령어 주소를 가르키는 레지스터의 값 - 레지스터 값: 프로세스의 레지스터 상태를 저장 - 스케줄링 정보: 프로세스의 우선순위, 스케줄링 알고리즘에 필요한 정보 - 할당된 자원 정보: 프로세스가 할당 받은 메모리, 입출력 장치, 파일 등의 자원 정보를 저장 - 프로세스 계정 정보: 프로세스의 사용 시간, CPU 시간, 생성 시간 등의 계정 정보를 포함 **마무리 정리** 1. Process 는 프로그램이 실행되는것을 Process 라고하고 좀더 명확하게 표현하면 Programe 의 instance 를 Process 고도 할 수있겠다 2. Process 는 Register, Stack , Heap 등의 여러가지 구조로 이루어져있다 3. 프로세스의 정보들은 PCB에 할당되어있다. ### 2. Thread 의 자원할당 > 자 이제 맨처음 나왔던 그림을 다시 보자 Thread 부분 말고는 왜 저런 그림으로 되어있는지 알수있을것이다. > > > 그럼 이제 Thread 에 대해서 알아보자 > - **Thread 란 뭐라고?(간략하게)** 프로세스의 최소단위의 작업(Task)를 실행하는것을 Thread 라고한다. **자원할당** Thread 는 앞서 말한것처럼 Process 와 관련이있기 때문에 **공유자원과 각각 독립적인 자원이 존재할수 있슴다.** Thread 이미지 (왼쪽이 single 오른쪽이 mulitThread) - **공유자원** 1. 메모리 - 프로세스의 메모리 공간을 공유합니다. 따라서, 스레드는 동일한 코드, 데이터, 힙을 공유하며, 이를 통해 변수, 배열, 객체 등의 데이터를 공유 2. 파일 디스크립터 - 여러 스레드는 동시에 파일에 대한 I/O 작업을 수행가능, 컴퓨터 시스템에서 파일이나 I/O 장치를 식별하고 조작하기 위해 사용되는 정수 값이라고 봐도 무방하다 3. 시그널 핸들러 - 프로세스의 시그널 핸들러를 공유합니다. 시그널은 프로세스에게 발생한 이벤트를 알리는 메커니즘이며, 스레드는 이를 처리하기 위해 동일한 시그널 핸들러를 사용가능 4. 열린 소캣 - 스레드는 프로세스의 열린 소켓을 공유합니다. 이는 네트워크 통신 작업을 스레드 간에 공유가능 - **독립자원** 1. **Thread Local 변수(이놈은 저희가 나중에 직접쓸수도 있습니다)** - Thread Local은 Java에서 스레드 간에 데이터를 공유하지 않고, 각 스레드별로 독립적으로 유지하고자 할 때 사용되는 기능이고 - Thread Local은 각 스레드마다 별도의 변수 인스턴스를 제공하며, 해당 변수는 스레드 내에서만 접근할 수 있습니다. - 각 스레드는 Thread Local 변수에 값을 설정하고 가져오는 메서드를 사용하여 독립적인 상태를 유지할 수 있습니다. - 예를 들어, 스레드 간에 로깅 정보, 트랜잭션 컨텍스트, **사용자 인증 정보** 등을 공유하지 않고 스레드 내에서만 유지하고자 할 때 Thread Local을 사용할 수 있습니다 - **이때 Thread Local 사용시 remove 를 하지 않는다면 어떻게 될까??** - **코드** ```java public class ThreadLocalExample { private static ThreadLocal