프로그래밍/- java
Thread
즐겁게 하하하
2022. 1. 31. 08:17
728x90


★ 프로세스 : 실행중인 프로그램
★ 쓰레드 : 실제 작업을 수행 , 모든 프로세스는 최소한 하나의 쓰레드를 가지고 있다.
thread는 각각 자신만의 작업 공간을 가짐 ( context )
각 thread 사이에서 공유하는 자원이 있을 수 있음 (자바에서는 static instance)
동기화( 일종의 순차적 수행)를 구현하지 않으면 오류가 발생할 수 있음
__________________________________________________________
쓰레드 -> 실행 가능한 상태로 만듬 -> OS 스케줄러가 실행 순서를 결정
쓰레드는 사용자 쓰레드와 데몬 쓰레드(보조 쓰레드) 가 있는데,
실행중인 사용자 쓰레드( main 등.. )가 하나도 없을때 프로그램은 종료된다.
main 메서드의 쓰레드가 종료되었다고 프로그램이 종료 되는것이 아니다.
__________________________________________________________
단점 : 1. 동기화에 주의해야 한다.
2. 교착상태가 발생하지 않게 주의해야 한다.
3. 각쓰레드가 효율적으로 고르게 실행될 수 있게 해야한다. : 기아 발생 주의..
__________________________________________________________
1. Thread 클래스 상속하여 만들기
2. Runnable 인터페이스 구현하여 만들기 ( 이미 다른걸 extends 한 경우 사용 )
class Mythread extends Thread{
public void run() {
for (int j = 0; j <= 5; j++) { System.out.print(j + "\t"); }
}
}
public class ThreadTest {
public static void main(String[] args) {
//Thread[main(호출한곳) , 5(우선순위) , main(쓰레드 그룹)]
System.out.println( Thread.currentThread() + " start");
Mythread th1 = new Mythread();
Mythread th2 = new Mythread();
th1.start();
th2.start();
System.out.println( Thread.currentThread() + " end");
}
}
======================================
class Mythread2 implements Runnable{
public void run() {
for (int j = 0; j <= 5; j++) { System.out.print(j + "\t"); }
}
}
public class ThreadTest2 {
public static void main(String[] args) {
System.out.println( Thread.currentThread() + " start");
Mythread2 runnable = new Mythread2();
Thread th1 = new Thread(runnable);
Thread th2 = new Thread(runnable);
th1.start();
th2.start();
System.out.println( Thread.currentThread() + " end");
}
}
======================================
class Mythread3 implements Runnable{
public void run() {
for (int j = 0; j <= 5; j++) { System.out.print(j + "\t"); }
}
}
public class ThreadTest3 {
public static void main(String[] args) {
System.out.println( Thread.currentThread() + " start");
Runnable run = new Runnable() {
@Override
public void run() { System.out.println("=== run ==="); }
};
run.run();
System.out.println( Thread.currentThread() + " end");
}
}
__________________________________________________________
A. synchronized( 동기화 )
- sleep(시간) <=> 시간이 지나면
- wait() <=> notify() : not Runnable 상태 Thread중 하나 깨어남
notifyAll() : not Runnable 상태 Thread 모두 깨운다
notifyAll() 메서드의 사용을 권장한다.
- join() <=> 지정한 쓰레드 작업이 모두 끝날때까지
ㄴ join을 걸면 자신이 not Runnable 상태에 빠지고, 지정된 쓰레드
작업(Runnable 상태가됨) 이 끝나면 Runnable 상태로 되돌아 온다
B. Lock , Condition 클래스 를 이용한 임계영역 설정 및 동기화
- ReentrantLock 클래스
. 재진입 가능한 lock, 가장 일반적인 배타 lock
. 특정 조건에서 lock을 풀고, 나중에 다시 lock을 얻어 임계영역으로 진입 가능
awit() == wait()
signal() == notify()
signalAll() == notifyAll()
- ReentrantReadWriteLock 클래스
. 읽기를 위한 lock과 쓰기를 위한 lock을 따로 제공
. 읽기에는 공유적이고, 쓰기에는 배타적인 lock
. 읽기 lock이 걸려있는 경우, 다른 스레드들도 읽기라면 진입 가능 (read-only)
. 읽기 lock이 걸려있는 경우, 다른 스레드가 쓰기 lock은 금지 (데이터 변경 방지)
- StampedLock 클래스
. ReentrantReadWriteLock에 낙관적인 lock의 기능을 추가
. 낙관적인 읽기 lock은 쓰기 lock에 의해 바로 해제 가능
. 무조건 읽기 lock을 걸지 않고, 쓰기와 읽기가 충돌할 때만 쓰기 후 읽기 lock
- Condition 클래스
. 특정 스레드를 위한 Conditon 인스턴스를 만들어 스레드 별로
wait pool을 만들어주는 기능을 한다.
. 이를 이용하여 깨우고 싶은 특정 스레드만 깨우는 것이 가능하다.
__________________________________________________________
쓰레드의 우선순위( 희망사항 :: 결정은 OS )
- 중요도에 따라 우선순위를 다르게 하여 특정 쓰레드가 더 많은 작업시간을 갖게 할 수 있다.
- 우선순위가 높을수록 빨리 끝날 가능성이 높아진다.
public static final int MAX_PRIORITY = 10 :: 최대 우선순위
public static final int MIN_PRIORITY = 1 :: 최소 우선순위
public static final int NORM_PRIORITY = 5 :: 보통 우선순위
void setPriority( int newPriority ) :: 쓰레드의 우선순위를 지정한 값으로 변경
int getPriority() :: 쓰레드의 우선순위를 반환
__________________________________________________________
쓰레드의 그룹
- 서로 관련된 쓰레드를 그룹으로 묶음
- 모든 쓰레드는 반드시 하나의 그룹에 포함되야 한다.
그룹을 지정하지 않으면 main 쓰레드 그룹에 속해진다.
- 자신을 생성한 쓰레드( 부모 )의 그룹과 우선순위(기본5)를 상속받는다.
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 e )
:: 처리되지 않은 예외에 의해 그룹의 쓰레드가 실행이 종료 되었을때 JVM에
의해 이 메서드가 자동으로 호출됨
__________________________________________________________
싱글 쓰레드 구현
- Thread 클래스 상속 ( 자바는 단일상속 )
Class MyThread extends Thread{
public void run() { // 오버라이딩
//작업 내용
System.out.println( this.getName() ); :: 조상Thread의 getName() 호출
}
}
MyThread t1 = new MyThread();
t1.start(); // 실행
__________________________________________________________
Runnable 인터페이스를 구현
Class MyThread implements Runnable {
public void run(){ // 추상메서드run
//작업내용
// Thread.currentThread() :: 현재 실행중인 Thread 반환
System.out.println( Thread.currentThread().getName() );
}
}
Runnable r = new MyThread();
Thread t1 = new Thread(r)
:: Thread t1 = new Thread( new MyThread() )
t1.start(); // 실행
__________________________________________________________
멀티 쓰레드 구현 ( 실행순서는 OS가 결정한다. )
- t1 과 t2 문맥교환(context Switching) 이 발생하여 단일 쓰레드 보다 시간이 더 걸린다.
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.start();
t2.start();
- 쓰레드의 I/O 블락킹( 입출력시 작업중단 )
사용자 입력을 받기 전까지 프로세스가 대기상태가 된다.
이 대기 상태가 되는동안 멀티 Thread를 이용하여 다른 작업을 수행 할 수 있다.
__________________________________________________________
데몬 쓰레드
- 일반 쓰레드의 작업을 돕는 보조 역할
- 일반 쓰레드가 모두 종료되면 자동 종료
- 가비지 컬렉터 , 자동저장, 화면 자동 갱신 등에 이용
- 무한루프와 조건문을 이용해 실행후 대기하다가 특정조건이 만족되면 작업수행후 대기.
- boolean isDaemon() 데몬 쓰레드 인지 확인 :: 데몬이면 true
- void setDaemon( boolean on)
:: 데몬 또는 사용자 쓰레드로 변경 true 로 지정시 데몬 쓰레드
:: 반드시 start 되기 전에 호출 되어야 한다.
__________________________________________________________
쓰레드 상태
NEW : 쓰레드가 생성되고 아직 START 호출안됨
RUNNABLE : 실행중, 실행가능한 상태
BLOCKED : 동기화 블럭에 의해 일시정지 된 상태
WAITING : 종료 되지는 않았지만 실행 가능하지 않은 일시 정지 상태
ㄴ TIMED_WAITING : WAITING 에서 일시 정지 시간이 지정된 경우
TERMINATED : 작업이 종료된 상태
__________________________________________________________
소요 시간 구하는 코드
long startTime = System.currentTimeMillis();
for(int i=0; i < 300; i++)
System.out.printf("%s", new String("-"));
System.out.print("소요시간1:" +(System.currentTimeMillis()- startTime));
__________________________________________________________
Thread 종료하기
- Thread를 종료할 때 사용함
- 무한 반복의 경우 while(flag)의 flag 변수값을 false로 바꾸어 종료를 시킴
쓰레드 이름
|
this.getName()
Thread.currentThread().getName() |
다른 쓰레드 기다리기
|
join() :: 작업이 모두 끝날때까지
join( long millis ) :: 지정된 시간동안 1/1000 Mythread th1 = new Mythread(); th1.start(); try { th1.join(); // main쓰레드가 th1의 작업이 끝날 때까지 기다린다. } catch( InterruptedException e ) { } Exception의 자손으로 InterruptedException 예외처리 필수 |
시간 1초간 잠자기 1/1000
|
static void sleep() :: 쓰레드 자신에게만 가능
void delay ( millis ){ try{ Thread.sleep( millis ); //Thread.sleep(1000); } catch ( InterruptedException e ) { } } Exception의 자손으로 InterruptedException 예외처리 필수 |
sleep 또는 join 에 의해 일시 정지
상태인 쓰래드 깨우기 |
* ex) file 다운로드 중단 , 재개
void interrupt() :: interrupted상태를 false 에서 true로 변경 boolean isInterrupted() :: interrupted상태를 반환 static boolean interrupted(0 :: 현재 쓰레드의 interrupted 상태 를알려주고 false로 초기화 Thread.interrupted(); |
남은 시간을 다음 쓰레드에게 양보하고
자신은 실행 대기 한다. interrupt()와 yield()를 적절히 사용하면 응답성과 효율을 높일 수 있다. |
static void yield();
Thread.yield(); ㅡ os 스케줄러 한테 통보만 하는것이지 선택은 os가 한다. |
즉시 종료
|
stop() :: 교착상태 빼지기 쉬워서 deprecated 됨
|
일시정지
|
suspend() :: 교착상태 빼지기 쉬워서 deprecated 됨
|
suspend 일시정지 해제
|
resume() :: 교착상태 빼지기 쉬워서 deprecated 됨
|
우선순위 정하기
순위 불러오기 |
MyThread th = new MyThread();
th.setPriority(7); // 우선순위7로 System.out.println( th.getPriority() ); |


import java.util.ArrayList;
class Fastibrary{
public ArrayList<String> self = new ArrayList<String>();
public Fastibrary() {
self.add("책1"); self.add("책2");
}
public synchronized String lendBook() throws InterruptedException {
Thread t = Thread.currentThread();
while(self.size() == 0) {
System.out.println(t.getName() + " Waiting");
wait();
System.out.println(t.getName() + " Waiting End");
}
if( self.size() > 0 ) {
String book = self.remove(0);
System.out.println(t.getName() + " : " + book + " lend");
return book;
}else { return null; }
}
public synchronized void returnBook( String book ) {
Thread t = Thread.currentThread();
self.add(book);
notify();
System.out.println(t.getName() + " : " + book + " return");
}
}
class Student extends Thread{
public Student(String string) { super.getName(); }
public void run() {
try {
String title = LibraryMain.library.lendBook();
if(title == null) { return; }
sleep(200);
LibraryMain.library.returnBook(title);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class LibraryMain {
public static Fastibrary library = new Fastibrary();
public static void main(String[] args) {
Student th1 = new Student("th1");
Student th2 = new Student("th2");
Student th3 = new Student("th3");
Student th4 = new Student("th4");
th1.start(); th2.start();
th3.start(); th4.start();
}
}
## ================ 데몬 쓰레드 ================
class Ex13_7 implements Runnable {
static boolean autoSave = false;
public static void main(String[] args) {
Thread t = new Thread(new Ex13_7());
t.setDaemon(true); // 이 부분이 없으면 종료되지 않는다.
t.start();
t.isInterrupted(); // true;
// t.interrupted(); :: 잘못된 입력.. static 메소드는 자신 Thread 호출
Thread.interrupted(); :: Main 쓰레드 interrupted상태를 알려주고 false로 초기화
for(int i=1; i <= 10; i++) {
try{
Thread.sleep(1000);
} catch(InterruptedException e) {}
System.out.println(i);
if(i==5) autoSave = true;
}
System.out.println("프로그램을 종료합니다.");
}
public void run() {
while(true) {
try {
Thread.sleep(3 * 1000); // 3초마다
} catch(InterruptedException e) {}
// autoSave의 값이 true이면 autoSave()를 호출한다.
if(autoSave) autoSave();
}
}
public void autoSave() {
System.out.println("작업파일이 자동저장되었습니다.");
}
}
## ================ 쓰레드 volatile ================
class Ex13_10 {
public static void main(String args[]) {
MyThread th1 = new MyThread("*");
MyThread th2 = new MyThread("**");
MyThread th3 = new MyThread("***");
th1.start(); th2.start(); th3.start();
try {
Thread.sleep(2000);
th1.suspend(); // 쓰레드 th1을 잠시 중단시킨다.
Thread.sleep(2000);
th2.suspend();
Thread.sleep(3000);
th1.resume(); // 쓰레드 th1이 다시 동작하도록 한다.
Thread.sleep(3000);
th1.stop(); // 쓰레드 th1을 강제종료시킨다.
th2.stop();
Thread.sleep(2000);
th3.stop();
} catch (InterruptedException e) {}
} // main
}
class MyThread implements Runnable {
volatile boolean stopped = false;
volatile boolean suspended = false;
## Multi Thread환경에서 Thread가 변수 값을 읽어올 때 각각의 CPU Cache에 저장된 값이
다르기 때문에 변수 값 불일치 문제가 발생하게 됩니다.
volatile 키워드를 추가하게 되면 Main Memory에 저장하고 읽어오기 때문에
변수 값 불일치 문제를 해결 할 수 있습니다.
## Multi Thread 환경에서 하나의 Thread만 read & write하고
나머지 Thread가 read하는 상황에서 가장 최신의 값을 보장합니다.
Thread th;
MyThread( String name ){
th = new Thread( this , name ); // Thread( Runnable r , String name )
}
void start() { th.start(); }
void stop() { stopped = true; }
void suspend() { suspended = true; }
void resume() { suspended = false; }
public void run() {
while(!stopped) {
if(!suspended) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch(InterruptedException e) {}
}
}
} // run()
}
## ================ 쓰레드 synchronized , wait() , notify() ================
import java.util.ArrayList;
class Customer2 implements Runnable { // 손님
private Table2 table;
private String food;
Customer2(Table2 table, String food) {
this.table = table;
this.food = food;
}
public void run() {
while(true) {
try { Thread.sleep(100);} catch(InterruptedException e) {}
String name = Thread.currentThread().getName();
table.remove(food);
System.out.println(name + " ate a " + food);
} // while
}
}
class Cook2 implements Runnable { // 요리사
private Table2 table;
Cook2(Table2 table) { this.table = table; }
public void run() {
while(true) {
int idx = (int)(Math.random()*table.dishNum());
table.add(table.dishNames[idx]);
try { Thread.sleep(10);} catch(InterruptedException e) {}
} // while
}
}
class Table2 {
String[] dishNames = { "donut","donut","burger" }; // donut의 확률을 높인다.
final int MAX_FOOD = 6;
private ArrayList<String> dishes = new ArrayList<>();
public synchronized void add(String dish) {
while(dishes.size() >= MAX_FOOD) {
String name = Thread.currentThread().getName();
System.out.println(name+" is waiting.");
try {
wait(); // COOK쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
}
dishes.add(dish);
notify(); // 기다리고 있는 CUST를 깨우기 위함.
System.out.println("Dishes:" + dishes.toString());
}
public void remove(String dishName) {
synchronized(this) {
String name = Thread.currentThread().getName();
while(dishes.size()==0) {
System.out.println(name+" is waiting.");
try {
wait(); // CUST쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
}
while(true) {
for(int i=0; i<dishes.size();i++) {
if(dishName.equals(dishes.get(i))) {
dishes.remove(i);
notify(); // 잠자고 있는 COOK을 깨우기 위함
return;
}
} // for문의 끝
try {
System.out.println(name+" is waiting.");
wait(); // 원하는 음식이 없는 CUST쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
} // while(true)
} // synchronized
}
public int dishNum() { return dishNames.length; }
}
class Ex13_15 {
public static void main(String[] args) throws Exception {
Table2 table = new Table2();
// 쓰레드 그룹 Thread( ThreadGroup group , String name )
new Thread(new Cook2(table), "COOK").start();
new Thread(new Customer2(table, "donut"), "CUST1").start();
new Thread(new Customer2(table, "burger"), "CUST2").start();
Thread.sleep(2000);
System.exit(0);
}
}
## ================ 쓰레드 synchronized , wait() , notify() ================
import java.util.ArrayList;
class Customer2 implements Runnable { // 손님
private Table2 table;
private String food;
Customer2(Table2 table, String food) {
this.table = table;
this.food = food;
}
public void run() {
while(true) {
try { Thread.sleep(100);} catch(InterruptedException e) {}
String name = Thread.currentThread().getName();
table.remove(food);
System.out.println(name + " ate a " + food);
} // while
}
}
class Cook2 implements Runnable { // 요리사
private Table2 table;
Cook2(Table2 table) { this.table = table; }
public void run() {
while(true) {
int idx = (int)(Math.random()*table.dishNum());
table.add(table.dishNames[idx]);
try { Thread.sleep(10);} catch(InterruptedException e) {}
} // while
}
}
class Table2 {
String[] dishNames = { "donut","donut","burger" }; // donut의 확률을 높인다.
final int MAX_FOOD = 6;
private ArrayList<String> dishes = new ArrayList<>();
public synchronized void add(String dish) {
while(dishes.size() >= MAX_FOOD) {
String name = Thread.currentThread().getName();
System.out.println(name+" is waiting.");
try {
wait(); // COOK쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
}
dishes.add(dish);
notify(); // 기다리고 있는 CUST를 깨우기 위함.
System.out.println("Dishes:" + dishes.toString());
}
public void remove(String dishName) {
synchronized(this) {
String name = Thread.currentThread().getName();
while(dishes.size()==0) {
System.out.println(name+" is waiting.");
try {
wait(); // CUST쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
}
while(true) {
for(int i=0; i<dishes.size();i++) {
if(dishName.equals(dishes.get(i))) {
dishes.remove(i);
notify(); // 잠자고 있는 COOK을 깨우기 위함
return;
}
} // for문의 끝
try {
System.out.println(name+" is waiting.");
wait(); // 원하는 음식이 없는 CUST쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
} // while(true)
} // synchronized
}
public int dishNum() { return dishNames.length; }
}
class Ex13_15 {
public static void main(String[] args) throws Exception {
Table2 table = new Table2();
// 쓰레드 그룹 Thread( ThreadGroup group , String name )
new Thread(new Cook2(table), "COOK").start();
new Thread(new Customer2(table, "donut"), "CUST1").start();
new Thread(new Customer2(table, "burger"), "CUST2").start();
Thread.sleep(2000);
System.exit(0);
}
}
728x90