25장 쓰레드는 개발자라면 알아두는 것이 좋아요
Thread
- 하나의 프로세스 내에 여러 쓰레드가 수행
- Thread = 경량 프로세스
- 프로세스가 하나 시작하려면 많은 자원이 필요하기 때문에 보다 빠른 처리가 필요할 때 쓰레드를 사용
쓰레드 생성
- 쓰레드가 수행되는 우리가 구현하는 메소드는 run() 메소드
- 쓰레드를 시작하는 메소드는 start()
- start() 메소드를 만들지 않아도 알아서 자바에서 run() 메소드를 수행하도록 되어있음
Runnable 인터페이스
- java.lang
리턴 타입 | 메소드 이름 및 매개변수 | 설명 |
void | run() | 쓰레드가 시작되면 수행되는 메소드 |
public class RunnableSample implements Runnable{
public void run(){
System.out.println("Runnable Sample run() method");
}
}
public class RunThreads{
public static void main(String[] args){
RunThreads threads = new RunThreads();
threads.runBasic();
}
public void runBasic(){
RunnableSample runnable = new RunnableSample();
new Thread(runnable).start();
}
}
- Thread 클래스 생성자에 해당 객체를 추가하여 start()
Thread 클래스
public class ThreadSample extends Thread{
public void run(){
System.out.println("Thread Sample run() method");
}
}
public class RunThreads{
public static void main(String[] args){
RunThreads threads = new RunThreads();
threads.runBasic();
}
public void runBasic(){
RunnableSample runnable = new RunnableSample();
new Thread(runnable).start();
ThreadSample thread = new ThreadSample();
thread.start();
System.out.println("RunThreads.runBasic() method is ended.");
}
}
- ThreadSample 클래스의 객체에 바로 start() 메소드 호출
// 결과 (다른 결과가 나올 수 있음)
Thread Sample run() method
RunThreads.runBasic() method is ended.
Runnable Sample run() method
→ 쓰레드 클래스가 다른 클래스를 확장할 필요가 있을 경우: Runnable 인터페이스 구현, 그렇지 않은 경우 Thread 클래스 사용
Thread 클래스의 생성자
생성자 | 설명 |
Thread() | 새로운 쓰레드 생성 |
Thread(Runnable target) | target 객체의 run() 메소드를 수행하는 쓰레드 생성 |
Thread(Runnable target, String name) | target 객체의 run() 메소드를 수행하고, name이라는 이름을 갖는 쓰레드 생성 |
Thread(String name) | name이라는 이름을 갖는 쓰레드 생성 |
Thread(ThreadGroup group, Runnable target) | group의 쓰레드 그룹에 속하는 target 객체의 run() 메소드를 수행하는 쓰레드 생성 |
Thread(ThreadGroup group, Runnable target, String name) | group의 쓰레드 그룹에 속하는 target 객체의 run() 메소드를 수행하고, name이라는 이름을 갖는 쓰레드 생성 |
Thread(ThreadGroup group, Runnable target, String name, long stackSize) | group의 쓰레드 그룹에 속하는 target 객체의 run() 메소드를 수행하고, name이라는 이름을 갖는 쓰레드를 생성. 단 해당 쓰레드의 스택의 크기는 stackSize만큼만 가능 |
Thread(ThreadGroup group, String name) | group의 쓰레드 그룹에 속하는 name이라는 이름을 갖는 쓰레드 생성 |
- 쓰레드 이름의 기본값 “Thread-n”
public class NameCalcThread extends Thread{
private int calcNumber;
public NameCalcThread(String name, int calcNumber){
super(name);
this.calcNumber=calcNumber;
}
public void run(){
calcNumber++;
}
}
- 쓰레드를 시작할 때 숫자를 넘겨주고 그 숫자를 run() 안에서 계산 → 매개변수 사용
sleep()
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
static void | sleep(long millis) | 매개 변수로 넘어온 시간(1/1000초)만큼 대기 |
static void | sleep(long millis, int nanos) | millis + nanos 만큼 대기 |
- 쓰레드는 run() 메소드가 끝나지 않으면 애플리케이션이 끝나지 않음 (데몬 쓰레드 제외)
public class EndlessThread extends Thread{
public void run(){
while(true){
try{
System.out.println(System.currentTimeMillis()); // 현재 시간을 밀리초 단위로 출력
Thread.sleep(1000); // 1초간 대기
}catch (InterruptedException e){ // sleep을 사용할 땐 try-catch 필수
e.printStackTrace();
}
}
}
}
- 1초에 한번씩 현재 시간을 밀리초 단위로 계속 출력
- main() 메소드의 수행이 끝나더라도 시작된 쓰레드가 종료하지 않으면 자바 프로세스는 끝나지 않음
Thread 클래스의 주요 메소드
1. 속성을 확인하고 지정하는 메소드
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
void | run() | 쓰레드가 시작되면 수행되는 메소드 |
long | getId() | 쓰레드의 고유 id를 리턴 (JVM 자동 생성) |
String | getName() | 쓰레드의 이름을 리턴 |
void | setName(String name) | 쓰레드의 이름을 지정 |
int | getPriority() | 쓰레드의 우선순위를 확인 |
void | setPriority(int newPriority) | 쓰레드의 우선순위를 지정 |
boolean | isDaemon() | 쓰레드가 데몬인지 확인 |
void | setDaemon(boolean on) | 쓰레드를 데몬으로 설정 |
StackTraceElement[] | getStackTrace() | 쓰레드의 스택 정보 확인 |
Thread.State | getState() | 쓰레드의 상태 확인 |
ThreadGroup | getThreadGroup() | 쓰레드의 그룹 확인 |
- 우선순위: 대기하고 있는 상황에서 더 먼저 수행할 수 있는 순위 (기본값 사용 권장)
- MAX_PRIORITY : 가장 높은 우선 순위 = 10
- NORM_PRIORITY : 일반 쓰레드의 우선 순위 = 5
- MIN_PRIORITY : 가장 낮은 우선 순위 = 1
데몬 쓰레드
: 어떤 쓰레드를 데몬으로 지정하면 그 쓰레드가 수행되고 있든 말든 상관없이 JVM이 끝날 수 있다
단, 해당 쓰레드가 시작하기(start()) 전에 데몬 쓰레드로 지정되어야 함
→ 해당 쓰레드가 종료되지 않아도 다른 실행중인 일반 쓰레드가 없다면 종료
Synchronized
- 어떤 클래스나 메소드가 쓰레드에 안전하려면, synchronized를 사용해야만 한다
- 메소드에서 인스턴스 변수를 수정하려고 할 때 synchronized 필요
1. synchronized methods : 메소드 자체를 synchronized로 선언하는 방법
public synchronized void plus(int value){
amount += value;
}
- 메소드 선언부에 synchronized가 있으면 동일한 객체의 이 메소드에 여러 개의 쓰레드가 접근해도 한 순간에는 하나의 쓰레드만 이 메소드를 수행 가능
2. synchronized statements : 메소드 내의 특정 문장만 synchronized로 감싸는 방법
Object lock = new Object(); // 잠금 처리를 하기 위한 객체 선언
public void plus(int value){
synchronized(lock){
amount += value;
}
}
public void minus(int value){
synchronized(lock){
amount -= value;
}
}
- synchronized() 이후에 있는 중괄호 내에 있는 연산만 동시에 여러 쓰레드에서 처리하지 않겠다는 의미
- lock: 한 명의 쓰레드만 일을 할 수 있도록 허용하고 쓰레드가 일을 다 처리하고 나오면 다른 쓰레드에게 기회를 주는 역할
CommonCalculate calc = new CommonCalculate();
ModifyAmountThread thread1 = new ModifyAmountThread(calc, true);
ModifyAmountThread thread2 = new ModifyAmountThread(calc, true);
// 아래의 경우는 synchronized를 안한 것과 같음
CommonCalculate calc1 = new CommonCalculate();
ModifyAmountThread thread1 = new ModifyAmountThread(calc1, true);
CommonCalculate calc2 = new CommonCalculate();
ModifyAmountThread thread2 = new ModifyAmountThread(calc2, true);
→ 인스턴스 변수가 선언되어 있는 객체를 다른 쓰레드에서 공유할 일이 없으면 synchronized를 쓸 필요가 없음
*StringBuffer는 synchronized 블록으로 주요 데이터 처리 부분을 감싸두었기 때문에 Thread safe
→ 하나의 문자열 객체를 여러 쓰레드에서 공유해야 하는 경우 사용
*StringBuilder는 synchronized가 사용되지 않아 Thread unsafe
→ 여러 쓰레드에서 공유할 일이 없을 때 사용
2. 쓰레드를 통제하는 메소드
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
Thread.State | getState() | 쓰레드의 상태 확인 |
void | join() | 수행중인 쓰레드가 중지할 때까지 대기 |
void | join(long millis) | millis만큼 대기 |
void | join(long millis, int nanos) | millis + nanos만큼 대기 |
void | interrupt() | 수행중인 쓰레드에 중지 요청 |
- Thread.State
상태 | 의미 |
NEW | 쓰레드 객체는 생성되었지만 시작 전 상태 |
RUNNABLE | 쓰레드가 실행중인 상태 |
BLOCKED | 쓰레드가 실행 중지 상태(모니터 락이 풀리기 기다리는 상태) |
WAITING | 쓰레드가 대기 중인 상태 |
TIMED_WAITING | 특정 시간만큼 쓰레드가 대기중인 상태 |
TERMINATED | 쓰레드가 종료된 상태 |
NEW → 상태 → TERMINATED의 라이프 사이클을 가짐
- join() 해당 쓰레드가 끝날 때까지 대기
thread.join(60000); // 1분 대기
- interrupt() : InterruptedException을 발생시키며 쓰레드를 중지
3. 상태 확인을 위한 메소드
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
void | checkAccess() | 현재 수행중인 쓰레드가 해당 쓰레드를 수정할 수 있는 권한인지 확인, 권한이 없다면 SecurityException 예외 발생 |
boolean | isAlive() | 해당 쓰레드의 run() 메소드가 종료되었는지 확인 |
boolean | isInterrupted() | run() 메소드가 정상적으로 종료되지 않고, interrupt() 메소드 호출을 통해 종료되었는지 확인 |
static boolean | interrupted() | 현재 쓰레드가 중지되었는지 확인 |
4. static 메소드
- JVM에서 사용되는 쓰레드의 상태들을 확인하는 메소드
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
static int | activeCount() | 현재 쓰레드가 속한 그룹의 쓰레드 중 살아있는 쓰레드의 개수를 리턴 |
static Thread | currentThread() | 현재 수행중인 쓰레드의 객체를 리턴 |
static void | dumpStack() | 콘솔 창에 현재 쓰레드의 스택 정보를 출력 |
5. Object 클래스에 선언된 쓰레드와 관련있는 메소드
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
void | wait() | 다른 쓰레드가 Object 객체에 대한 notify() 메소드나 notifyAll() 메소드를 호출할 때까지 현재 쓰레드가 대기 |
void | wait(long timeout) | 밀리초 단위로 지정한 시간만큼 대기 |
void | wait(long timeout, int nanos) | 밀리초+나노초 단위로 지정한 시간만큼 대기 |
void | notify() | Object 객체의 모니터에 대기하고 있는 단일 쓰레드를 깨운다 |
void | notifyAll() | Object 객체의 모니터에 대기하고 있는 모든 쓰레드를 깨운다 |
public class StateThread extends Thread{
private Object monitor; // 인스턴스 변수
public StateThread(Object monitor){
this.monitor = monitor;
}
public void run(){
try{
for(int loop = 0; loop < 10000; loop++){
String a = "A";
}
synchronized(monitor){
monitor.wait(); // 쓰레드 대기
}
System.out.println(getName() + " is notified.");
Thread.sleep(1000); // wait()이 끝나면 1초간 대기 후 쓰레드 종료
}catch(InterruptedException ie){
ie.printStackTrace();
}
}
}
public class RunObjectThreads{
public static void main(String args[]){
RunObjectThreads sample = new RunObjectThreads();
sample.checkThreadState();
}
public void checkThreadState(){
Object monitor = new Object();
StateThread thread = new StateThread(monitor);
try{
System.out.println("thread state = " + thread.getState());
thread.start(); // thread 객체 생성 후 시작
System.out.println("after start = " + thread.getState());
Thread.sleep(100);
System.out.println("after 0.1 sec = " + thread.getState());
synchronized(monitor){
monitor.notify(); // 쓰레드 깨우기
}
Thread.sleep(100);
System.out.println("after notify = " + thread.getState());
thread.join(); // 쓰레드가 종료될 때까지 기다린 후 상태를 출력
System.out.println("after join = " + thread.getState());
}catch (InterruptedException ie){
ie.printStackTrace();
}
}
}
// 결과
thread state = NEW
after start = RUNNABLE
after 0.1 sec = WAITING
Thread-0 is notified
after notify = TIMED_WAITING
after join = TERMINATED
6. ThreadGroup에서 제공하는 메소드
- ThreadGroup: 쓰레드의 관리를 용이하게 하기 위한 클래스
- 쓰레드 그룹은 tree 구조를 가짐
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
int | activeCount() | 실행 중인 쓰레드의 개수를 리턴 |
int | activeGroupCount() | 실행 중인 쓰레드 그룹의 개수를 리턴 |
int | enumerate(Thread[] list) | 현재 쓰레드 그룹에 있는 모든 쓰레드를 list에 저장 |
int | enumerate(Thread[] list, boolean recurse) | 현재 쓰레드 그룹에 있는 모든 쓰레드를 list에 저장하고 true이면 하위에 있는 쓰레드 그룹에 있는 쓰레드 목록도 포함 |
int | enumerate(ThreadGroup[] list) | 현재 쓰레드 그룹에 있는 모든 쓰레드 그룹을 list에 저장 |
int | enumerate(ThreadGroup[] list, boolean recurse) | 현재 쓰레드 그룹에 있는 모든 쓰레드 그룹을 list에 저장하고 true이면 하위에 있는 쓰레드 그룹 목록도 포함 |
String | getName() | 쓰레드 그룹의 이름을 리턴 |
ThreadGroup | getParent() | 부모 쓰레드 그룹을 리턴 |
void | list() | 쓰레드 그룹의 상세 정보를 출력 |
void | setDaemon(boolean daemon) | 지금 쓰레드 그룹에 속한 쓰레드들을 데몬으로 지정 |
27장 Serializable과 NIO도 살펴 봅시다
Serializable 인터페이스
- 파일을 읽거나 쓸 수 있도록 하거나, 다른 서버로 보내거나 받을 수 있도록 하려면 Serializable 인터페이스를 구현해야 함
- Serializable 인터페이스를 구현 후 serialVersionUID 값을 지정해 주는 것을 권장
- 해당 객체의 버전을 명시하는 데 사용
- 무조건 static final long serialVersionUID 로 선언해야 함
static final long serialVersionUID = 1L;
transient
: transient로 선언한 변수는 Serializable 대상에서 제외
→ 보안상 중요한 변수나 꼭 저장해야 할 필요가 없는 변수에 사용
transient private int bookOrder;
NIO
- 채널과 버퍼를 사용해 속도를 높임
- ByteBuffer라는 버퍼와 FileChannel이라는 채널을 사용해 파일 데이터를 처리
NIO Buffer 클래스
리턴 타입 | 메소드 | 설명 |
int | capacity() | 버퍼에 담을 수 있는 크기를 리턴 |
int | limit() | 버퍼에서 읽거나 쓸 수 없는 첫 위치 리턴 |
int | position() | 현재 버퍼의 위치 리턴 |
- java.nio.Buffer 클래스를 확장
- 현재의 “위치”를 나타내는 메소드 position()
- 읽거나 쓸 수 없는 “위치”를 나타내는 메소드 limit()
- 버퍼의 크기를 나타내는 메소드 capacity()
- 0 ≤ mark ≤ position ≤ limit ≤ capacity
리턴 타입 | 메소드 | 설명 |
Buffer | flip() | limit 값을 현재 position으로 지정한 후 position을 0으로 이동 |
Buffer | mark() | 현재 position을 mark |
Buffer | reset() | 버퍼의 posotion을 mark한 곳으로 이동 |
Buffer | rewind() | 현재 버퍼의 position을 0으로 이동 |
int | remaining() | limit-position 계산 결과를 리턴 |
boolean | hasRemaining() | position과 limit 값에 차이가 있을 경우 true 리턴 |
Buffer | clear() | 버퍼를 지우고 현재 position을 0으로 이동하고 limit 값을 버퍼의 크기로 변경 |
- flip()은 limit값을 변경하지만 rewind()는 limit값을 변경하지 않는다
- remaining()과 hasRemaining()은 limit까지만 데이터를 읽을 수 있다
'STUDY > Java' 카테고리의 다른 글
성적 관리 프로그램 예제1 - 2021.09.03 (0) | 2023.12.09 |
---|---|
<자바의 신> 28장 다른 서버로 데이터를 보내려면 어떻게 하면 되나요? (1) | 2023.12.09 |
<자바의 신> 21장 실수를 방지하기 위한 제네릭이라는 것도 있어요 (0) | 2023.12.09 |
<자바의 신> 16장 클래스 안에 클래스가 들어갈 수도 있구나 (0) | 2023.12.09 |
<자바의 신> 11장 매번 만들기 귀찮은데 누가 만들어 놓은 거 쓸 수 없나요? (0) | 2023.12.09 |