[자바] 스레드(Thread)

allnight5·2022년 12월 2일
0

자바 개념정리

목록 보기
21/28

Java의 정석 기초편에서 참고

스레드 클래스 생성시
1.22-12-14수정

extends Thread를 활용한 방법과

class MultiThreadThing extends Thread{
	@override
    public void run(){
    	System.out.println("Thread Test"); 
        try{
        	Thread.sleep(1000);
        }catch(InterruptedExcption e){
        
        }
}

implements Runnable을 활용하는 방법

class MultiThreadThing implements Runnable{
	@override
    public void run(){
    	System.out.println("Thread Test"); 
        try{
        	Thread.sleep(1000);
        }catch(InterruptedExcption e){
        
        }
    }
}

기본으로
1.extends Thread를 활용한 방법과
2.implements Runnable을 활용하는 방법
이렇게 두가지가 있는데
상속을 한다면 다른 클래스의 상속을 받을수 없다.

그러니 인터페이스 형태의 Runnable을 사용하고
main이나 이 클래스의 스래드를 돌리는곳에서 아래와 같이 활용을한다면
Runnable을 이용하여 스레드를 사용하는것이 가능하다
한마디로 Runnalbe로 구현하는것이 재사용성이 높고 코드의 일관성을 유지할수 있기 때문에
보다 객체지향적인 방법이다.

쓰레드 그롭의 메서드


생성자/메서드 설명
ThreadGroup(String name) 지정된 이름의 새로운 쓰레드 그룹을 생성
ThreadGroup

(ThreadGroup parent, String name)

지정된 쓰레드 그룹에 포함되는 새로운 쓰레드 그룹을 생성
int activeCount() 쓰레드 그룹에 포함된 화성상태에 있는 쓰레드으 ㅣ수를 반환
int activeGroupCount() 쓰레드 그룹에 포함된 활성상태에 있는 쓰레드 그룹의 수를 반환
void checkAccess() 현재 실행중인 쓰데르가 쓰레드 그룹을 변경할 권환이 있는지 체크, 만일 권한이 없다면 SecutiryException을 발생시킨다.
void destroy 쓰레드 그룹과 하위 쓰레드 그룹까지 모두 삭제한다. 단, 쓰레드 그룹이나 하위 쓰레드 그룹이 비어있어야 한다.
int enumerate(Thread[] list)
int enumerate(Thread[] list, boolean recurse)
int enumerate(ThreadGroup[] list)
int enumerate(ThreadGroup[] list, boolean recurse)
쓰레드 그룹에 속한 쓰레드 또는 하위 쓰레드 그룹의 목록을 지정된 배열에 담고 그 개수를 반환,
두번재 매게변수인 recurse 의 값을true로 하면 쓰레드 그룹에 속한 하위 쓰레드 그룹에 쓰레드 또는 쓰레드 그룹까지 배열에 담는다.
int getMaxPriority() 쓰레드 그룹의 최대 우선순위를 반환
String getName() 쓰레드 그룹의 이름을 반환
ThreadGroup getParent() 쓰레드 그룹의 상위 쓰레드 그룹을 반환
void interrupt() 쓰레드 그룹에 속한 모든 쓰레드를 interrupt
boolean isDaemon() 쓰레드 그룹이 데몬 쓰레드 그룹인지 확인
boolean isDestoryed() 쓰레드 그룹이 삭제 되었는지 확인
void list() 쓰레드 그룹에 속한 쓰레드와 하위 쓰레드 그룹에 대한 정보를 출력
boolean parentOf(ThreadGroup g) 지정된 쓰레드 그룹의 상위 쓰레드 그룹인지 확인
void setDaemon(boolean daemon) 쓰레드 그룹을 데몬 쓰레드 그룹으로 설정/해제
void setMaxPriority(int pri) 쓰레드 그룹의 최대 우선순위를 설정

Runnable활용시 사용하는 곳에서 Thread형태로 활용하는 방법

MultiThreadThing myThing = new MultiThreadThing();
Thread myThread = new Thread(myThing);
myThread.start();

start말고도 Thread에는 .join()과 .isAvlie()?; 등이 있다.

싱글스레드와 멀티스레드

싱글코어에서 단순히 CPU만을 사용하는 계산작업이라면 멀티스레드보다 싱글스레드가 더 효율적이다 그이유는 시간은 거의 비슷하지만 멀티스레드의 경우 작업 전환을 할때 실행해야할 위치(PC,프로그램 카운터)드으이 정보를 저장하고 읽어오는 시간이 소요된다. 스레드의 스위칭에 비해 프로세스의 스위칭이 더 많은 정보를 저장해야하므로 더 많은 시간이 소요된다.
프로세스 또는 스레드 간의 작업전환을 '컨텍스트 스위칭' 이라고 한다.

그러나 CPU에서 계산작업만 하는게 아니라 두 스레드가 서로 다른 자원을 사용하는 작업의 경우 멀티스레드가 싱글 스레드에비하여 더 효율적이다 한스레드는 입력 받는 작업을 하고 한스레드는 네트워크에서 데이터를 받아온다고 할때 입력스레드에서 잠시 대기하는 동안 네트워크 스레드에서 데이터를 받아오는 작업을 멀티스레드는 가능하지만 싱글스레드는 입력이 끝날때까지 기다려야하기때문에 이럴경우에는 멀티스레드가 더 효율적이다.

스레드 우선순위 지정

우선순위를 지정해줘서 우선순위가 높은 스레드가 먼저 돌게 하게할수있다.
스레드가 가질수 있는 우선순위의 범위는 1~10이며 숫자가 높을수록 우선순위가 높다.
선언해주지 않을시 보통우선순위인 5가 기본으로 들어간다.
//예시 원래는 클래스에 extends Thread한 클래스를 불러와서 Thread자리에 넣어줘야한다.
Thread th1 = new Thread();
Thread th2 = new Thread();

th1.setPriority(7)
th2.setPriority(6)
th1이 우선순위가 높아 th1이 다끝나야 th2가 실행된다. 싱글스레드의 경우에는 말이다
멀티스레드이며 멀티 코어의 경우 다 돌아가고 있는것이 아니라면 다른 코어에서 스레드를 돌린다.

멀티스레드를 활용하려면 동기화에 대해서도 공부해보자


동기화의 사전뜻은 작업들 사이의 수행시기를 맞추는것
A라는 컴퓨터가 있고 B라는 스마트폰이 있다. 이 둘의 사진과 영상 자료를 서로 동일하게 만드는 작업이 바로 동기화 라고 하는것이 사전의 뜻으로
동기화작업 통하여 둘 중에 하나가 고장나더라도 다른 하나를 통하여 작업을 이어나갈수 있다.
이제 스레드 관점에서 동기화의 경우
참조사이트1
참조사이트2

스레드 동기화의 필요성


여러가지 작업에서 공통적으로쓰이는 데이터에 대하여 한 번에 여러 스레드가 접근한다면, 프로그램이 예상과는 다른 방향으로 꼬여버릴 수 있다. 이러한 점을 예방하기 위한 기능이 스레드 동기화(Thread Synchronization)이다. 공유데이터에 대하여 스레드들의 동시 접근을 방지하는 해결책

예로 들어 Object의 wait()-notify()메서드가 있다.
wait() 다른 스레드가 이 객체의 notify()를 불러줄때까지 대기한다.
notify() 이 객체에 대기중인 스레드를 깨운다 2개 이상의 스레드가 대기중이라면 랜덤으로 하나의 스레드를 개운다.
notifyAll() 이 객체에 대기 중인 모든 스레드를 깨운다. 단 한번에 모두 깨우는 것이 아니라, 임의로 배정된 순서대로 꺠운다.
※ 위의 3가지 메서드는 필수적으로 synchronized 임계영역 안에 있어야 한다. ※

두가지 관점에서의 스레드 동기화


실행순서의 동기화

쓰레드의 실행순서를 정의하고, 이 순서에 반드시 따르도록 하는 것이 쓰레드 동기화이다.

메모리 접근에 대한 동기화

한 순간에 하나의 쓰레드만 접근해야 하는 메모리 영역이 존재한다.
대표적으로 데이터 영역과 힙 영역이다.
데이터 영역에 할당된 변수를 둘 이상의 쓰레드가 동시에 접근할 때에는 문제가 발생한다.
계산 결과가 덮어써지는 문제가 발생했었다.
즉, 이렇게 메모리 접근에 있어서 동시 접근을 막는 것 또한 쓰레드의 동기화에 해당한다.

차이점은?

상황이 다르다.
실행순서의 동기화는 실행, 접근의 순서가 이미 정해져 있는 상황이다. 그리고 그 순서가 반드시 지켜져야 한다.
메모리 접근의 동기화는 실행 순서가 중요한 상황이 아니고, 한 순간 하나의 쓰레드만 접근하면 되는 상황을 의미한다.
즉 메모리 동시 접근 문제만 발생하지 않으면 된다.

쓰레드 동기화에 있어서 두 가지 방법


Windows에는 다양한 동기화 기법을 제공하는데 크게 두가지로 나뉜다.

1. 유저 모드 동기화 (User Mode Synchronize)

동기화가 진행되는 과정에서 커널의 힘을 빌리지 않는 (커널 코드가 실행되지 않는) 동기화 기법이다.
따라서 동기화를 위해서 커널 모드로의 전환이 불필요해 성능상의 이점이 있다.
그러나 그만큼 기능상의 제한도 있다.

2. 커널 모드 동기화 (Kernel Mode Synchronize)

커널에서 제공하는 동기화 기능을 활용하는 방법이다.
따라서 동기화에 관련된 함수가 호출될 때마다 커널 모드로의 변경이 필요하고, 이는 성능의 저하로 이어지게 된다.
하지만 그만큼 유저 모드 동기화에서 제공하지 못하는 기능을 제공받을 수 있다.

데몬 쓰레드


데몬 쓰레드는 다른 일반 쓰레드(데몬 쓰레드가 아닌쓰레드)의 작업을 돕는 보조적인 역할을 수행하는 쓰레드이다. 일반쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 종료된다. 그 이유는 일반쓰레드의 보조역할을 수행하는데 일반쓰레드가 모두 종료되고나면 데몬 쓰레드의 존재의 의미가 없기 때문이다.
이 점을 제외하고나면 데몬 쓰레드와 일반 쓰레드는 다르지않다.

데몬 쓰레드는 무한루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정 조건이 만족되면 수행하고 다시 대기하도록 작성한다.

public void run(){
	while(true){
    	try{
        	Thread.sleep(3*1000);
        }catch(InterrputedException e){}
    	if(autoSave) autoSave();//boolean변수오 void 클래스
    }
}

데몬 쓰레드는 일반 쓰레드의 작성방법과 실행 방법이 같으며 다만 쓰레드를 생성한 다음 실행하기 전에 setDaemon(true)를 호출하기만 하면된다. 그리고 데몬 쓰레드가 생성한 쓰레드는 자동적으로 데몬 쓰레드가 된다는 점도 알아두자 해제 하고 싶다면 setDaemon(false)로 해제하면 된다.

쓰레드의 실행제어


메서드 설명
static void sleep(Long mills) 지정된 시간(천분의 일초 단위)동안 쓰레드를 일시정지시킨다. 지정된 시간이 지나고 나면, 자동적으로 다시 실행 대기 상태가 된다.
void Join()
void join(long millis)
void join(long millis, int nanos)
지정된 시간동안 쓰레드가 실행되도록한다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 돌아와 실행을 계속한다.
void interrupt() sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다. 해당 쓰레드에서는 interruptd Exception이 발생함으로써 일시정지상태를 벗어나게 된다.
void stop() 쓰레드를 즉시 종료시킨다.
void suspend() 쓰레드를 일시정지시킨다. resume()을 호출하면 다시 실행대기 상태가 된다.
void resume() suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기 상태로 만든다.
static void yield() 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보(yield)하고 자신은 실행대기 상태가 된다.

sleep()


sleep()은 지정된 시간동안 쓰레드를 멈추게한다.
sleep()을 호출할때는 항상 try-catch(-finally)문으로 예외처리를 해줘야한다. 매번 처리해주는 것이 번거 롭기때문에 try-catch문까지 포함하는 새로운 메서드를 만들어서 사용한다.

void delay(long millis){
	try{
    	Thread.sleep(millis);
    }catch(InterruptedException e){}
}

interrupt()

void interrupt() 쓰레드의 interrupted상태를 false에서 true로 변경
boolean isInterrupted() 쓰레드의 interrupted상태를 반환
static boolean interrupted() 현재 쓰레드의 interrupted상태를 반환 후, false로 변경

suspend(),resume(),stop()

suspend()는 sleep()처럼 쓰레드를 멈추게한다. suspend로 정지된 쓰레드는 resume()을 호출해야 다시 실행 대기 상태가 된다. stop()은 호출되는 즉시 쓰레드가 종료된다. 이 메서드들은 교착상태를 일으키기 쉽게 장성되어 있으므로 사용이 권장 되지 않으며 그래서 이 메서드들은 모두 deprecated(전에는 사용되었지만, 앞으로는 사용하지 않을 것을 권장한다.) 되었다.

join()과 yield()

join() 다른 쓰레드의 작업을 기다린다.
try{
th1.join()//현재 실행중인 쓰레드가 쓰레드 th1의 작업이 끝날때까지 기다린다.
}catch(InterruptedException e){}
sleep과 유사한 점이 많으나 다른점은 join()은 현재 쓰레드가 아닌 특정 쓰레드에 의해 동작하므로 static메서드가 아니라는 것이다.

yield()다른 쓰레드에게 양보한다.
yield()는 쓰레드 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보(yield)한다.1초의 실행시간을 할당받은 쓰레드가 0.5초 잡업한 상태에서 yield가 호출되면 나머지 0.5초는 포기하고 다시 실행대기 상태가 된다.
yield()와 interrupt()를 적절히 사용하면 프로그램의 응답성을 높이고 보다 효율적인 실행이 가능하게 할 수 있다.

쓰레드의 동기화(synchronization)


쓰레드 A가 작업 하던 도중 쓰레드B에게 제어권이 넘어갔을때 쓰레드A가 작업하던 공유데이터를 쓰레드 B가 임의로 변경하였다면, 다시 쓰레드A가 제어권을 받아서 나머지 작업을 마쳤을때 원래 의도했던 것과 다른 결과를 얻을수있다.
이러한 일이 발생하는 것을 방지하기 위해서 한 쓰레드가 특정 작업을 끝마치기 전까지 다른 쓰레드에 의해 방해받기 않도록 하는것이 필요해서 도입된 개념이 바로 임계영역(critical section)잠금(lock) 이다.
한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는것을 '쓰레드의 동기화'라고 한다.

동기화를 할때 변수는 private로 해야 동기화가 의미가 있다.

wait()와 notify()


동기화해서 공유데이터를 보호하는 것 까지는 좋은데 특정 쓰레드가 객체의 락을 가진 상태로 오랜시간 보내지 않도록 하는것도 중요하다.동기화된 임계영역의 코드를 수행하다가 작업을 더이상 진행할 상황이 아니면 일단 wait()을 호출하여 쓰레드가 락을 반납하고 기다리게 한다. 그러면 다른 쓰레드가 락을 얻어 해당 객체에 대한 작업을 수행할 수 있게 된다. 나중에 작업을 진행할 수 있는 상황이 되면 notify()를 호출해서, 작업을 중단했던 쓰레드가 다시 락을 얻어 작업을 진행 할 수있게한다.
notifyAll()은 기다리고 있는 모든 쓰레드에게 통보하지만, lock을 얻을 수 있는 것은 하나의 쓰레드일뿐 나머지 쓰레드는 통보를 받긴했지만, lock을 얻지 못하면 다시 lock을 기다리는 신세가 된다.

wait(), notify(), notifyAll()
-Object에 정의되어있다.
-동기화 블록(synchronized블록)내에서만 사용할 수 있다.
-보다 효율적인 동기화를 가능하게 한다.

profile
공부기록하기

0개의 댓글