프로세스와 스레드
- 프로세스 : 실행 중인 프로그램 -> 프로그램을 실행하면 OS로부터 실행에 필요한 자원을 할당받아 프로세스가 된다
- 프로세스는 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 그리고 스레드로 구성되어 있으며, 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이 스레드
- 둘 이상의 쓰레드를 가진 프로세스를 '멀티쓰레드 프로세스'라 칭한다
- 멀티쓰레딩 : 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것
- 멀티쓰레딩의 장점:
1. CPU의 사용률을 향상시킨다.
2. 자원을 보다 효율적으로 사용할 수 있다.
3. 사용자에 대한 응답성이 향상된다.
4. 작업이 분리되어 코드가 간결해진다.
스레드의 구현과 실행
- 쓰레드를 구현하기 위해선 Thread클래스를 상속받거나 Runnable인터페이스를 구현하면 된다.
- Thread 클래스를 상속받으면 다른 클래스를 상속받을 수 없기에, Runnable인터페이스를 구현하는 방법이 일반적이라고 한다.
- Runnable인터페이스를 구현하는 방법은 재사용성이 높고 코드의 일관성을 유지할 수 있어 보다 객체지향적인 방법
class MyThread extends Thread{
public void run(){ /*작업내용 */ }
}
class MyThread implements Runnable{
public void run() {/*작업내용*/}
}
- 쓰레드를 구현한다는 것은, 쓰레드를 통해 작업하고자 하는 내용으로 run()의 몸통을 채우는 것.
package ch13;
public class ThreadEx1 {
public static void main(String[] args) {
ThreadEx1_1 t1 = new ThreadEx1_1();
Runnable r = new ThreadEx1_2();
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
class ThreadEx1_1 extends Thread{
public void run(){
for(int i=0; i <5; i++){
System.out.println(getName()); // 조상 Thread의 getName()호출
}
}
}
class ThreadEx1_2 implements Runnable{
@Override
public void run() {
for(int i=0; i < 5; i++){
//Thread.currentThread()는 현재 실행중인 Thread를 반환합니다.
System.out.println(Thread.currentThread().getName());
}
}
}
- 쓰레드를 상속받으면 자손 클래스에서 조상인 Thread클래스의 메서드를 직접 호출할 수 있다
- Runnable을 구현하면 Thread클래스의 static메서드인 currentThread()를 호출하여 쓰레드에 대한 참조를 얻어 와야만 호출이 가능하다.
- static Thread currentThread() : 현재 실행중인 쓰레드의 참조를 반환한다.
- String getName() : 쓰레드의 이름을 반환한다.
-쓰레드의 이름을 지정하지 않으면 'Thread - 번호'의 형식으로 이름이 정해진다.
- start()를 호출하고 실행대기중인 쓰레드가 없다면 곧바로 실행상태가 된다.
- 한번 실행이 된 쓰레드는 다시 실행할 수 없으며, 쓰레드의 작업을 한 번더 수행해야한다면 새로운 쓰레드를 생성한 다음에 start()를 한번 더 호출해야 한다.
쓰레드의 우선순위
- 쓰레드는 우선순위라는 속성을 가지고 있는데, 이 우선 순위의 값에 따라 쓰레드가 얻는 실행시간이 달라진다.
ex ) 파일 전송이 있는 메신저의 경우 파일전송보다 채팅 내용을 전송하는 쓰레드의 우선순위가 일반적으로 더 높다.
- void setPriority(int newPriority) : 쓰레드의 우선순위를 지정한 값으로 변경한다.
- int getPriority() : 쓰레드의 우선순위를 반환한다.
- 쓰레드의 우선순위는 쓰레드를 생성한 쓰레드로부터 상속받는다.
package ch13;
public class ThreadEx8 {
public static void main(String[] args) {
ThreadEx8_1 th1 = new ThreadEx8_1();
ThreadEx8_2 th2 = new ThreadEx8_2();
th2.setPriority(7);
System.out.println("Priority of th1(-) :" + th1.getPriority());
System.out.println("Priority of th2(-) : " + th2.getPriority());
th1.start();
th2.start();
}
}
class ThreadEx8_1 extends Thread {
public void run() {
for (int i = 0; i < 300; i++) {
System.out.println("-");
for (int x = 0; x < 10000000; x++) ;
}
}
}
class ThreadEx8_2 extends Thread {
public void run() {
for (int i = 0; i < 300; i++) {
System.out.println("|");
for (int x = 0; x < 1000000; x++)
}
}
}
-> 싱글코어냐, 멀티코어냐에 따라 결과가 다르게 나옴. 싱글코어에서는
쓰레드 그룹
- 쓰레드 그룹은 서로 관련된 쓰레드를 그룹으로 다루기 위한 것으로, 폴더를 생성해서 관련된 파일을 함께 넣어서 관리하는 것처럼, 쓰레드 그룹을 생성해서 쓰레드를 그룹으로 묶어 관리할 수 있다.
- 보안상의 이유로 도입되었으며, 다른 쓰레드 그룹의 쓰레드를 변경할 수 없다.
- 쓰레드를 쓰레드 그룹으로 포함시키려면, Thread의 생성자를 이용해야 한다.
- 모든 쓰레드는 반드시 쓰레드 그룹에 포함되어 있다.
- 쓰레드 그룹을 지정하는 생성자를 사용하지 않은 쓰레드는 기본적으로 자신을 생성한 스레드와 같은 쓰레드 그룹에 속하게 된다.
생성자 / 메서드 | 설명 |
ThreadGroup(String name) | 지정된 이름의 새로운 쓰레드 그룹을 생성 |
ThreadGroup(ThreadGroup parent, String name) | 지정된 쓰레드 그룹에 포함되는 새로운 쓰레드 그룹을 생성 |
int activeCount() | 쓰레드 그룹에 포함된 활성상태에 있는 쓰레드의 수를 반환 |
int activeGroupCount() | 쓰레드 그룹에 포함된 활성상태에 있는 쓰레드 그룹의 수를 반환 |
void checkAccess() | 현재 실행중인 쓰레드가 쓰레드 그룹을 변경할 권한이 있는지 체크. 만일 권한이 없다면 SecurityException을 발생시킴. |
void destroy() | 쓰레드 그룹과 하위 쓰레드 그룹까지 모두 삭제 |
int enumerate(Thread[] list) | 쓰레드 그룹에 속한 쓰레드 또는 하위 쓰레드 그룹의 목록을 지정된 배열에 담고 그 개수를 반환. |
int getMaxPriority() | 쓰레드 그룹의 최대 우선순위를 반환 |
String getName() | 쓰레드 그룹의 이름을 반환 |
void list() | 쓰레드 그룹에 속한 쓰레드와 하위 쓰레드 그룹에 대한 정보를 출력 |
void setMaxPriority(int pri) | 쓰레드 그룹의 최대 우선순위를 설정 |
Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
ThreadGroup getThreadGroup() // 쓰레드 자신이 속한 쓰레드 그룹을 반환한다.
void uncaughtException(Thread t, Throwable t) // 쓰레드 그룹의 쓰레드가 처리되지 않은 예외에 의해 실행이 종료되었을때ㅡ JVM에 의해 이 메서드가 자동으로 호출된다.
package ch13;
public class ThreadEx9 {
public static void main(String[] args) {
ThreadGroup main = Thread.currentThread().getThreadGroup();
ThreadGroup grp1 = new ThreadGroup("Group1");
ThreadGroup grp2 = new ThreadGroup("Group2");
ThreadGroup subGrp1 = new ThreadGroup(grp1,"SubGroup1");
grp1.setMaxPriority(3); // 쓰레드그룹 grp1의 최대 우선순위를 3으로 변경
Runnable r = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
}catch(InterruptedException e){}
}
};
new Thread(grp1, r, "th1").start();
new Thread(subGrp1, r,"th2").start();
new Thread(grp2,r,"th3").start();
System.out.println(">>List of ThreadGroup: " + main.getName() +", Active ThreadGroup: " + main.activeGroupCount() + ", Active Thread: " + main.activeCount());
main.list();
}
}
- 하위 쓰레드 그룹이나 쓰레드는 들여쓰기를 이용해서 구분되도록 하였다.
- setMaxPriority()를 통해 쓰레드 그룹 grp1과 그 하위의 쓰레드의 최대 우선순위를 3으로 설정되게 만들었다.
쓰레드의 실행 제어
- 쓰레드의 상태:
1. NEW : 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태
2. RUNNABLE : 실행 중 또는 실행 가능한 상태
3. BLOCKED : 동기화 블럭에 의해서 일시정지된 상태 (lock이 풀릴 때까지 기다리는 상태)
4. WAITING, TIME WAITING : 실행가능하지 않은 일시정지 상태, TIMED_WAITING은 일시정지시간이 지정된 경우를 의미함
- 쓰레드를 생성하고 start()를 호출하면, 바로 실행되는 것이 아니라 실행대기열에 저장되어 자신의 차례가 올때까지 기다려야한다.
-실행 대기 상태에 있다가 자신의 차례가 되면 실행상태가 된다.
- 주어진 실행시간이 지나거나 yield()를 만나면 다시 실행대기상태가 되고, 다음 차례의 스레드가 실행상태가 된다.
- 실행중에 suspend(), sleep() ,wait(), join(), I/O block에 의해 일시정지 상태가 될 수 있다.
- timeout(), notify(), resume(), interrupt()가 호출되면 일시정지상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다.
- 실행을 마치거나, stop()이 호출되면 스레드는 소멸된다.
메서드 | 설명 |
static void sleep(long millis) static void sleep(long millis, int nanos) |
지정된 시간동안 쓰레드를 일시정지시킨다. 지정한 시간이 지나고 나면, 자동적으로 실행대기상태가 된다. |
void join() void join(long millis) void join(long millis, int nanos) |
지정된 시간동안 쓰레드가 실행되도록 한다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속 한다. |
void interrupt() | sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다. |
void stop() | 쓰레드를 즉시 종료시킨다. |
void suspend() | 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다. |
void resume() | suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다. |
static void yield() | 자신에 게 주어진 실행시간을 다른 쓰레드에게 양보하고 자신은 실행대기 상태가 된다. |
쓰레드의 동기화
- 멀티 스레드 프로세스의 경우 여러 스레드가 같은 프로세스 내의 자원을 공유해서 작업하면 서로의 작업에 영향을 줄 위험이 있음
- 이를 방지하기 위해 한 스레드가 다른 스레드에 의해 방해 받지 않도록 하는 것이 필요한데, 이것이 임계영역, 락이다.
- 공유 데이터를 사용하는 코드 영역을 임계 영역으로 지정해놓고, 공유 데이터의 lock을 획득한 단 하나의 쓰레드만 이 영역 내의 코드를 수행할 수 있게 한다.
- 이처럼 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 '쓰레드의 동기화'라고 한다.
synchronized를 이용한 동기화
- 메서드 앞에 synchronized를 붙이면, 메서드 전체가 임계 영역으로 설정된다. 쓰레드는 synchronized메서드가 호출된 시점부터 해당 메서드가 포함된 객체의 lock을 얻어 작업을 수행하다, 메서드가 종료되면 lock을 반환한다.
- 코드 일부를 블럭{}으로 감싸고 블럭 앞에 'synchronized' 참조변수를 붙이는 것이다. 이 블럭 영역 안으로 들어가면서부터 쓰레드는 지정된 객체의 lock을 얻게 되고, 이 블럭을 벗어나면 lock을 반납하게 된다.
package ch13;
public class ThreadEx21 {
public static void main(String[] args) {
Runnable r = new RunnableEx21();
new Thread(r).start();
new Thread(r).start();
}
}
class Account{
private int balance = 1000;
public int getBalance(){
return balance;
}
public void withdraw(int money){
if(balance >= money){
try{Thread.sleep(1000);} catch(InterruptedException e){}
balance -= money;
}
}
}
class RunnableEx21 implements Runnable{
Account acc = new Account();
public void run(){
while(acc.getBalance() > 0){
int money = (int) (Math.random() * 3 + 1) * 100;
acc.withdraw(money);
System.out.println("balance: "+acc.getBalance());
}
}
}
- 출금을 수행하려는 순간 다른 쓰레드에게 제어권이 넘아가서 다른 쓰레드가 200을 출금하여 잔고가 0이 되었다.
- 이러한 상황을 개선하기 위해, 즉 한 쓰레드의 작업이 다른 쓰레드에 의해 영향받지 않도록 하려면 synchronized키워드를 사용하여야 한다.
package ch13;
public class ThreadEx21 {
public static void main(String[] args) {
Runnable r = new RunnableEx21();
new Thread(r).start();
new Thread(r).start();
}
}
class Account{
private int balance = 1000;
public int getBalance(){
return balance;
}
public synchronized void withdraw(int money){
if(balance >= money){
try{Thread.sleep(1000);} catch(InterruptedException e){}
balance -= money;
}
}
}
class RunnableEx21 implements Runnable{
Account acc = new Account();
public void run(){
while(acc.getBalance() > 0){
int money = (int) (Math.random() * 3 + 1) * 100;
acc.withdraw(money);
System.out.println("balance: "+acc.getBalance());
}
}
}
'Language > Java' 카테고리의 다른 글
네트워킹 (0) | 2022.02.12 |
---|---|
입출력 I/O (0) | 2022.01.31 |
Generics (0) | 2022.01.10 |
Collection Framework (0) | 2022.01.02 |
날짜와 시간 & 형식화 date, time and formatting (0) | 2021.12.25 |