반응형
Mutual Exclusion
- 하나의 스레드가 수정 중인 공유자원을 사용이 끝나기 전에 다른 스레드가 수정하면 안된다.
1.volatile
- 변수 앞에 붙이는 예약어로 변수가 연산에 사용될 때 원자성을 보장
- 원자성: 작업이 완전히 끝나기 전에는 다른 작업이 끼어들 수 없다.
- 32bit 시스템에서 64bit 자료형인 long, double 을 멀티 스레드 환경에서 사용하다 보면 작업이 완료되기 전에 다른 스레드에서 사용해서 잘못된 결과를 사용할 수 있습니다.
- 이 경우에 volatile을 붙여주면 위와 같은 상황이 발생하지 않습니다.
2.synchronized 메소드
- 메소드 결과형 앞에 synchronized를 붙이면 메소드를 동기화해서 실행
- 이 메소드가 완료되기 전에는 다른 스레드가 수행될 수 없도록 합니다.
3.synchronized 블럭
synchronized(인스턴스){
코드
}
- 블럭 내에서 인스턴스를 사용하는 부분만 동기화가 됩니다.
- 한 번에 이루어져야 하는 부분만 동기화 할 수 있습니다.
생산자와 소비자 문제
- 공유 자원을 생성하는 스레드와 사용하는 스레드가 동시에 동작 중일 때 소비자 스레드는 생산자 스레드가 공유 자원을 생성을 해주었을 때 동작을 해야 합니다.
- 공유 자원을 생성하지 않은 상태에서 소비자 스레드가 동작하게 되면 예외가 발생합니다.
1.wait 메소드
스레드의 작업을 대기 시키는 메소드
wait()
: notify()가 호출될 때 까지 대기wait(long msec)
: 매개변수로 대입된 시간만큼 대기
2.notify 메소드
- 대기 중인 스레드에게 신호를 보내서 작업을 수행하도록 해주는 메소드
notify()
: wait 중인 스레드 중 1개에게만 신호를 보내는 메소드notifyAll()
: wait 중인 모든 스레드에게 신호를 보내는 메소드
3.주의할 점
- wait 와 notify 메소드는 Object 클래스의 메소드
- wait 와 notify는 synchronized 메소드에서만 동작
- 메소드가 synchronized 메소드가 아니면 예외가 발생합니다.
4.실습
1) 공유 자원 클래스
- List를 인스턴스로 변수로 갖고 List에서 데이터 1개를 꺼내서 출력하는 메소드 와 List에 데이터 1개를 저장하는 메소드를 소유
//공유 자원을 소유할 클래스
public class Product {
//공유자원 변수
private List<Character> list;
//생성자 - list를 초기화
public Product() {
list = new ArrayList<Character>();
}
//데이터 1개를 받아서 저장하는 메소드
public void put(Character ch) {
list.add(ch);
System.out.println(ch + "가 입고 되었습니다.");
try {
Thread.sleep(1000);
}catch(Exception e) {}
System.out.println("입고 후 현재 수량:" + list.size());
}
//데이터 1개를 꺼내서 출력하는 메소드
public void get() {
//첫번째 데이터를 삭제하고 리턴
Character ch = list.remove(0);
System.out.println(ch + "가 출고 되었습니다.");
try {
Thread.sleep(1000);
}catch(Exception e) {}
System.out.println("출고 후 현재 수량:" + list.size());
}
}
2) 생산자 스레드
- Product를 생성자의 매개변수로 받아서 스레드를 이용해서 26번 데이터를 삽입
- Thread 클래스로부터 상속받는 Producer 클래스
public class Producer extends Thread {
private Product product;
//외부에서 인스턴스 주입받아서 객체를 생성하는 생성자
public Producer(Product product) {
this.product = product;
}
//스레드로 동작하는 메소드
public void run() {
for(char i = 'A'; i <= 'Z'; i++) {
product.put(i);
}
}
}
3) 소비자 스레드
- Product를 외부에서 받아서 스레드로 26번 get 동작
- Thread 클래스로부터 상속받는 Customer
public class Customer extends Thread {
//공유 자원 변수
private Product product;
//외부에서 주입받는 생성자
public Customer(Product product) {
this.product = product;
}
public void run() {
for(int i=0; i<26; i=i+1) {
product.get();
}
}
}
4) main 메소드를 소유한 Main 클래스
public class Main {
public static void main(String[] args) {
//공유 자원 생성
Product product = new Product();
//Thread 클래스를 상속받은 클래스의 인스턴스를 만들고 스레드로 동작
Customer customer = new Customer(product);
Producer producer = new Producer(product);
customer.start();
producer.start();
}
}
5) 이 상태에서 실행하면 customer 스레드가 예외를 발생시킵니다.
- product에 데이터가 없는데 읽을려고 해서 예외 발생
- 특정 조건을 만족하지 않으면 wait를 호출해서 대기모드로 만들고 조건이 만족되면 notify를 호출해서 wait 중인 스레드를 깨워주면 됩니다.
6) Product 클래스의 메소드를 수정
//데이터 1개를 받아서 저장하는 메소드
//synchronize 가 붙지 않으면 jvm이 모니터링을 하지 않기 때문에 신호를 보낼 수 없습니다.
public synchronized void put(Character ch) {
//2개가 넘으면 대기
if(list.size() > 2) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(ch);
//데이터 추가 후 신호를 보냅니다.
notify();
System.out.println(ch + "가 입고 되었습니다.");
try {
Thread.sleep(1000);
}catch(Exception e) {}
System.out.println("입고 후 현재 수량:" + list.size());
}
//데이터 1개를 꺼내서 출력하는 메소드
public synchronized void get() {
//list에 데이터가 없으면 대기
if(list.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//첫번째 데이터를 삭제하고 리턴
Character ch = list.remove(0);
notify();
System.out.println(ch + "가 출고 되었습니다.");
try {
Thread.sleep(1000);
}catch(Exception e) {}
System.out.println("출고 후 현재 수량:" + list.size());
}
Semaphore
- 공유자원을 동시에 사용할 수 있는 스레드의 개수를 설정할 수 있는 클래스
- 이 클래스를 이용하면 동시에 수행되는 스레드의 개수를 지정 가능
1.생성자
Semaphore(int permit)
: 동시에 수행될 스레드 개수
2.메소드
acquire()
: 공유자원에 Lock을 설정하는 메소드, 이 메소드가 호출되면 공유자원 개수가 1개 줄어듭니다.release()
: 공유자원에 Lock을 해제하는 메소드, 이 메소드가 호출되면 공유자원 개수가 1개 늘어납니다.
3.작업 방법
- Semaphore 클래스의 인스턴스를 스레드 외부에서 생성
- Semaphore 인스턴스를 스레드에게 전달해서 공유자원을 사용하는 부분에서 이용
1) 세마포어가 적용된 스레드 클래스
public class ThreadEx extends Thread {
//세마포어 변수
private Semaphore sem;
//외부에서 Semaphore를 주입받습니다.
public ThreadEx(Semaphore sem) {
this.sem = sem;
}
public void run() {
try {
//lock을 취득 - 사용 개수가 1개 줄어듬
sem.acquire();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("몬스터 생성");
try {
Thread.sleep(3000);
}catch(Exception e) {}
System.out.println("몬스터 소멸");
//lock을 해제 - 사용 개수가 1개 늘어남
sem.release();
}
}
2) main 메소드
public class SemaphoreMain {
public static void main(String[] args) {
//동시에 3개 까지 실행되는 세마포어 생성
Semaphore sem = new Semaphore(3);
for(int i=0; i<20; i=i+1) {
ThreadEx th = new ThreadEx(sem);
th.start();
}
}
}
병렬 처리
- 동시에 2개 이상의 작업을 수행하는 것
- 자바에서는 fork&join 프레임워크(1.7) 와 스트림 API(1.8)에서 제공
- 1부터 100까지의 합계를 구하는 경우 프로세서가 2개 이상이라면 1-50까지를 하나의 프로세서가 계산하고 다른 프로세서가 51-100까지의 합을 계산해서 더하면 훨씬 효율적으로 작업을 할 수 있게 됩니다.
- 작성 방법
- 작업을 수행하고 리턴 여부에 따라서 RecursiveAction(리턴 값이 없는 경우) 이나 RecursiveTask(리턴 값이 있는 경우) 클래스를 상속받는 클래스를 생성하고 compute 메소드를 재정의해서 작업을 어떻게 분할해서 수행할 지 작성하고 결과를 리턴하면 됩니다.
- RecursiveTask는 리턴할 데이터 타입으로 제너릭을 적용해야 하고 compute의 리턴 타입도 일치해야 합니다.
반응형
'Language_Study > JAVA' 카테고리의 다른 글
[JAVA, App] 17.자바GUI (0) | 2020.12.27 |
---|---|
[JAVA, App] 16.이벤트처리 (0) | 2020.12.27 |
[JAVA, App] 14.기본API클래스 (0) | 2020.12.27 |
[JAVA, App] 13.Framework (0) | 2020.12.25 |
[JAVA, App] 12.Interface (0) | 2020.12.25 |