일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 스파르타코딩
- Java
- programmers
- 면접
- JPA
- 웹개발
- 알고리즘
- Bean Validation
- 브루트 포스
- db
- 네트워크
- 쿼리dsl
- 코테
- 자료구조
- ModelAttribute
- 메서드
- 스프링
- 자바
- 코딩테스트
- 객체지향프로그래밍
- 스프링MVC
- 프로그래머스
- 반복문
- 정처기
- OS
- 운영체제
- 스프링 MVC
- 검증
- 자바의 정석
- 백준
- Today
- Total
개발일지
쓰레드 본문
목차
쓰레드
프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이다.
프로세스 : 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 그리고 쓰레드로 구성되어 있음
쓰레드 구현과 실행
쓰레드 구현 방법
1. Thread클래스 상속 → 다른 클래스는 상속받을 수 없음
class MyThread extends Thread {
public void run(){ // Thread 클래스의 run()을 오버라이딩
}
2. Runnable인터페이스 구현 → 일반적으로 쓰레드를 구현하는 방법
class MyThread implements Runnalbe {
public void run() {// Runnable인터페이스의 run() 구현}
}
- 재사용성이 높고 코드의 일관성을 유지할 수 있기에 보다 객체지향적인 방법이다.
- 오로지 run()만 정의되어 있는 간단한 인터페이스이다.
- Runnalbe인터페이스를 구현한 클래스의 인스턴스를 생성한 다음, 이 인스턴스를 Thread클래스의 생성자 매개변수로제공해야 한다.
Thread클래스 상속과 Runnable 인터페이스 구현 예시
package javaStandard.chapter13;
public class Ex13_1 {
public static void main(String[] args) {
ThreadEx1_1 t1 = new ThreadEx1_1(); // Thread의 자손 클래스의 인스턴스 생성 -> ThreadEx1_1 인스턴스 생성
Runnable r = new ThreadEx1_2(); // Runnalbe을 구현한 클래스의 인스턴스 생성
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());
}
}
}
class ThreadEx1_2 implements Runnable {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
쓰레드의 실행 - start()
- 쓰레드를 생성했다고 해서 자동으로 실행되는 것이 아니며, start()를 호출해야 쓰레드가 실행된다.
- start()가 호출되었다고 해서 바로 실행되는 것이 아니라, 실행대기 상태에 있다가 자신의 차례가 되어야 실행된다.
- 또한, 한번 실행이 종료된 쓰레드는 다시 실행할 수 없기에 하나의 쓰레드에 대해 start()가 한번만 호출될 수 있다.
- 하나의 쓰레드에 대해 start()를 두번 이상 호출하면 IlleagalThreadStateException이 발생한다.
쓰레드의 우선순위
- 쓰레드는 우선순위(priority)라는 속성(멤버변수)을 가지고 있는데, 이 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라진다. 중요도와 우선순위를 다르게 지정하여 특정 쓰레드가 더 많은 작업시간을 갖도록 할 수 있다.
main메서드를 수행하는 쓰레드는 우선순위가 5이므로 main메서드 내에서 생성하는 쓰레드의 우선순위는 자동적으로 5가됨
void setPriority(int newPriority) // 쓰레드의 우선순위를 지정한 값으로 변경한다.
int getPriority() // 쓰레드의 우선순위를 반환한다.
public static final int MAX_PRIORITY = 10 // 최대 우선순위
public static final int MIN_PRIORITY = 1 // 최소 우선순위
public static final int NORM_PRIORITY = 5 // 보통 우선순위
데몬 쓰레드
- 데몬쓰레드는 다른 일반 쓰레드의 작업을 돕는 보조적인 역할을 수행하는 쓰레드이다.
- 일반 쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 자동 종료된다.
- 데몬 쓰레드는 무한루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.
- 데몬 쓰레드 생성한 다음실행하기 전에 setDaemon(true)를 호출하기만 하면된다.
쓰레드의 실행제어
sleep()
- sleep()은 지정된 시간동안 쓰레드를 멈추게 한다.
- sleep()을 호출할 때는 항상 try-catch문으로 예외를 처리해줘야 한다.
static void sleep(long millis) // 밀리 세컨드(1000분의 일초)
static void sleep(long millis, int nanos) // 나노 센컨드(10억분의 일초)
void delay(long millis) {
try{
Thread.sleep(millis);
} catch(InterruptedException e) {}
}
join()
- join()은 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 한다.
- 시간을 지정하지 않으면, 해당 쓰레드가 작업을 모두 마칠 때까지 기다리게 된다.
- 작업 중에 다른 쓰레드의 작업이 먼저 수행되어야 할 필요가 있을 때 join()을 사용한다.
- join()도 sleep()처럼 interrupt()에 의해 대기상태에서 벗어날 수 있으며, join()이 호출되는 부분을 try-catch문으로 감싸야 한다.
void join()
void join(long millis)
void join(long millis, int nanos)
try {
th.join();
} catch(InterruptedException e) {}
쓰레드의 동기화(synchroniaztion)
- 공유 데이터를 사용하는 코드 영역을 임계 영역으로 지정해놓고, 공유 데이터가 가지고 있는 lock을 획득한 단 하나의 쓰레드만 이 영역 내의 코드를 수행할 수 있게 한다.
- 그리고 해당 쓰레드가 임계 영역 내의 모든 코드를 수행하고 벗어나서 lock을 반납해야만 다른 쓰레드가 반납된 lock을 획득하여 임계 영역의 코드를 수행할 수 있게 된다.
- 이처럼 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 쓰레드의 동기화라고 한다.
synchronized를 이용한 동기화
1. 메서드 전체를 임계 영역으로 지정
쓰레드는 synchronized메서드가 호출된 시점부터 해당 메서드가 포함된 객체의 lock을 얻어 작업을 수행하다가 메서드가 종료되면 lock을 반환한다.
2. 특정한 영역을 임계 영역으로 지정
메서드 내의 코드 일부를 블럭{}으로 감싸고 블럭 앞에 synchronized를 붙이는 것인데, 이때 참조변수는 락(lock)을 걸고자하는 객체를 참조하는 것이어야 한다.
1. public synchronized void calcSum() { // 임계 영역 } // 메서드 전체를 임계 영역으로 지정
2. synchronized(객체의 참조변수) { // 임계 영역} // 특정한 영역을 임계 영역으로 지정
- 모든 객체는 lock을 하나씩 가지고 있으며, 해당 객체의 lock을 가지고 있는 쓰레드만 임계 영역의 코드를 수행할 수 있다.
- 임계 영역은 멀티 쓰레드 프로그램의 성능을 좌우하기 때문에 가능하면 메서드 전체에 락을 거는 것보다 synchronized블럭으로 임계 영역을 최소화해서 보다 효율적인 프로그램이 되도록 해야 한다.
synchronized 적용 전
package javaStandard.chapter13;
public class Ex_12 {
public static void main(String[] args) {
Runnable r = new RunnableEx12();
new Thread(r).start();
new Thread(r).start();
}
}
class Account {
private int balance = 100;
public int getBalance() {
return balance;
}
public void withdraw(int money) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
balance -= money;
}
}
}
class RunnableEx12 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());
}
}
}
결과
balance: 100
balance: 100
balance: 100
balance: -100
balance: 0
실행결과를 보면 잔고가 음수되는 것을 볼 수 있는데, 그 이유는 한 쓰레드가 if문의 조건식을 통화하고 출금하기 바로 직전에 다른 쓰레드가 끼어들어서 출금을 먼저했기 때문이다.
synchronized 적용후
package javaStandard.chapter13;
public class Ex_12 {
public static void main(String[] args) {
Runnable r = new RunnableEx12();
new Thread(r).start();
new Thread(r).start();
}
}
class Account {
private int balance = 100;
public int getBalance() {
return balance;
}
public synchronized void withdraw(int money) {
if (balance >= money) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
balance -= money;
}
}
}
class RunnableEx12 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());
}
}
}
결과
balance: 100
balance: 100
balance: 100
balance: 100
balance: 0
balance: 0
한 가지 주의할 점은 Account클래스의 인스턴스변수인 balance의 접근 제어자가 private이라는 것이다.
만일 private이 아니라면, 외부에서 직접 접근할 수 잇기 때문에 아무리 동기화하더라도 이 값의 변경을 막을 길이 없다.
⭐synchronized를 이용한 동기화는 지정된 영역의 코드를 한 번에 하나의 쓰레드가 수행하는 것을 보장하는 것이다.
'Java > Java 개념 설명' 카테고리의 다른 글
기본 자료형 VS 참조 자료형 (0) | 2024.02.20 |
---|---|
JVM에 대해 알아보자 (0) | 2023.04.10 |
제네릭스 (0) | 2023.03.24 |
예외처리 (0) | 2023.03.18 |
객체 지향 프로래밍Ⅱ(추상 클래스, 인터페이스) (0) | 2023.03.18 |