Process (공장): 실행 중인 프로그램, 자원(메모리, cpu 등) + thread 프로세서를 할당받아서 실행되는 것 한 번 실행되면 자신의 작업이 종료될 때 까지 제어권을 다른 프로세스에게 넘기지 않고 계속 수행 절대로 멈추지 않음
Thread (일꾼): Process를 작게 나누어서 작업을 수행하는 단위 Thread는 단독으로 실행할 수 없고Process 안에서 실행되야 함 자신의 작업 도중 쉬는 시간이 생기거나 일정한 시간이 지나면 다른 스레드에게 제어권을 양도할 수 있음 동시에 처리하는 것은 아님 (그건 Parallel), 다른 Thread가 동시에 수행하는 것은 가능
## Thread 클래스를 상속받으면 다른 클래스를 상속받을 수 없기 때문에, Runnable 인터페이스를 구현하는 방법이 일반적 - run()을 오버라이딩 하여 그 안에 작업 내용을 적으면 됨
1. Thread 클래스로부터 상속받는 클래스를 만들고 public void run 이라는 메서드에 스레드로 수행할 내용을 작성한 후 인스턴스를 만들고start메서드를 호출 - run 메서드의 내용을 수행(run메서드 호출하면 안됨, 스레드가 아닌 프로세스처럼 동작되므로)
2. Runnable 인터페이스로부터 상속받는 클래스를 만들고 public void run 이라는 메서드에 스레드로 수행할 내용을 작성한 후 Thread 클래스의 인스턴스를 만들 때 생성자에 생성한 클래스의 인스턴스를 대입하고 Thread 클래스의 인스턴스가 start 메서드를 호출하면 됨
실행 순서는 OS의 스케줄러가 작성한 스케줄에 의해 결정 (임의로), 어떤 순서에 의해 동시성으로 실행할 것인가?
한번 종료된 쓰레드는 다시 실행할 수 없음, 두 번 호출하면 IllegalThreadStateException 발생
하나의 쓰레드에 대해 start()가 한 번만 호출 될 수 있으므로 쓰레드 작업을 한 번 더 수행해야 한다면 새로운 쓰레드를 생성한 후 start() 호출해야 함
run() 메서드를 작성했는데 왜 start()를 호출 하지? - run()을 호출하는 것은 생성된 쓰레드를 실행시키는 것이 아니라 단순히 클래스에 선언된 메서드 호출 - start()는 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택(call stack)을 생성한 후 run() 호출 ▶ 모든 쓰레드는 독립적인 작업을 수행하기 위해 자신만의 호출스택을 필요로 함
1. main 메서드에서 쓰레드의 start() 호출 2. start()는 새로운 쓰레드 생성, 작업하는데 사용될 호출 스택 생성 3. 호출 스택에 run() 호출되어 쓰레드가 독립된 공간에서 작업 수행 4. 호출 스택이 2개 이므로 스케줄러가 정한 순서에 의해 번갈아 가면서 실행
package chapter13;
publicclassThreadCreate{
publicstaticvoidmain(String[] args){
ThreadCreate_1 t1 = new ThreadCreate_1();
Runnable r = new ThreadCreate_2();
Thread t2 = new Thread(r);
//Thread t2 = new Thread(new ThreadCreate_2()); 한줄로
t1.start();
t2.start();
}
} //end mainclassThreadCreate_1extendsThread{
@Overridepublicvoidrun(){
for(int i = 0; i < 5; i++) {
System.out.println(getName());
}
}
} //end ThreadCreate_1classThreadCreate_2implementsRunnable{
@Overridepublicvoidrun(){
for(int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName());
//currentThread() - 현재 실행중인 쓰레드의 참조 반환//getName() - 쓰레드의 이름 반환//자동적으로 이름은 Thread-n(숫자) 설정
}
}
} //end ThreadCreate_2
-----------------------------------------------------------------------
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
ThreadCreate -1. Thread 클래스로부터 상속받는 클래스 생성
<java />
package _api.thread;
//Thread 클래스로부터 상속받는 클래스를 생성classThreadExextendsThread{
//public void run 이라는 오버라이딩@Override//위의 어노테이션은 상위 클래스 나 인터페이스에서 제공하는 메서드가 아닌 경우//에러를 발생시켜 주는 어노테이션임publicvoidrun(){
//스레드로 수행할 내용//1초마다 Thread 클래스라는 문장을 10번 실행for(int i = 0; i < 10; i++) {
try {
Thread.sleep(1000); //try, catch 예외 안에 작성
System.out.println("Thread 클래스");
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
}
publicclassThreadCreate{
publicstaticvoidmain(String[] args){
//Thread 클래스로부터 상속받은 클래스를 이용해서 스레드를 생성하고 실행
ThreadEx th = new ThreadEx();
//start를 호출하면 run 메서드의 내용을 수행
th.start();
}
}
run메서드 호출하면 Thread가 아닌 Process처럼 동작되므로 start() 호출
ThreadCreate -2. Runnable 인터페이스로부터 상속받는 클래스
3.Runnable 인터페이스 -Anonymous Class를 이용
4.Runnable 인터페이스 -람다를 이용해서 작성
<java />
package _api.thread;
//Runnable 인터페이스를 구현한 클래스를 생성classRunnableImplimplementsRunnable{
@Overridepublicvoidrun(){
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(1000); //try, catch 예외 안에 작성
System.out.println("Runnable 인터페이스");
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
}
publicclassThreadCreate{
publicstaticvoidmain(String[] args){
//Runnable 인터페이스를 implements 클래스를 이용해서 스레드를 생성하고 실행
Thread th2 = new Thread(new RunnableImpl());
th2.start();
//Runnable 인터페이스를 Anonymous Class를 이용해서 사용
Thread th3 = new Thread(new Runnable() {
@Overridepublicvoidrun(){
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(1000); //try, catch 예외 안에 작성
System.out.println("Anonymous 활용");
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
});
th3.start();
//Runnable 인터페이스는 public void run 메서드 1개만 소유//람다를 이용해서 작성하는 것도 가능
Thread th4 = new Thread(() -> {
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(1000); //try, catch 예외 안에 작성
System.out.println("Lambda 활용");
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
});
th4.start();
}
}
ThreaGroup 이라는 클래스를 제공하지만 몇 몇 메서드가 제대로 동작하지 않는 문제 때문에 대부분의 경우 배열이나 List를 이용해서 구현하는 것을 권장
스레드 그룹에 다른 스레드 그룹을 포함시킬 수 있음
자바 어플리케이션이 실행되면, JVM은 main과 system이라는 스레드 그룹을 만들고 JVM 운영에 필요한 스레드들을 생성해서 이 스레드 그룹에 포함시킴 main 메서드 수행하는 main이라는 이름의 스레드 - main스레드 그룹 가비지컬렉션을 수행하는 Finalizer 스레드 - system 스레드 그룹
<java />
ThreadGroup getThreadGroup()//스레드 자신이 속한 스레드 그룹 반환voiduncaughtException(Thread t, Throwable e)//처리되지 않은 예외에 의해 스레드 그룹의 스레드가 실행종료 되었을 때,//JVM에 의해 이 메서드가 자동적으로 호출
시간이 다 되거나 interrupt()가 호출되거나 InterrupedException이 발생되면 잠에서 깨거나 실행대기 상태가 됨
이 메서드를 사용할 때는 InterrupedException 을 처리해주어야 함 → 항상 try ~ catch문으로 예외처리 필수
실습을 할 때 이 메서드를 사용하는 이유는 일정 시간 이상 대기를 해야만 다른 스레드에게 제어권이 이동되기 때문
실제 시간의 의미는 거의 없음
<java />
//매번 예외처리 하는 것은 번거롭기 때문에 메서드 만들어서 사용voiddelay(long msec){
try {
Thread.sleep(msec);
} catch(InterruptedException e) {} //Exception의 자손, 필수 예외 처리//예외와 try~catch를 이용해서 잠자는 상태에서 벗어나게 하기 위해 사용하므로 아무것도 쓰지 않아도 됨
}
특정 스레드를 지정해서 멈추게 하는 것은 불가능 (static, 자기 자신만)
<java />
package chapter13;
publicclassSleepOfThread{
publicstaticvoidmain(String[] args){
SleepOfThread_1 th1 = new SleepOfThread_1();
SleepOfThread_2 th2 = new SleepOfThread_2();
th1.start();
th2.start();
try {
//main 스레드에서 실행되고 있기 때문에 th1이 sleep되는 게 아님//th1.sleep(2000); 오류는 없지만 오해할 수 있음
Thread.sleep(2000);
} catch (InterruptedException e) {}
System.out.print("<<main 종료>>");
}// end main
}
classSleepOfThread_1extendsThread{
@Overridepublicvoidrun(){
for(int i = 0; i < 300; i++) {
System.out.print("-");
}
System.out.print("<<th1 종료>>");
}
} //end SleepOfThread_1classSleepOfThread_2extendsThread{
@Overridepublicvoidrun(){
for(int i = 0; i < 300; i++) {
System.out.print("|");
}
System.out.print("<<th2 종료>>");
}
} //end SleepOfThread_2
---------------------------------------------------------------------------------------
------------||||||||||||||||||||||-----------------------------------------------------
------------------------------------------------------------------------------------|||
|||||||------|||||||||||||||||||||--------------------------||-------------------------
----------------------------------------------||||||--------------|||||||||||||||------
----------------------------||||||<<th1 종료>>|||||||||||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|||<<th2 종료>><<main 종료>>
publicstaticvoidmain(String[] args){
ThreadEx th1 = new ThreadEx();
th1.start();
...
th1.interrupt(); //interrupted 상태를 false → true 변경
...
System.out.println("isInterrupted() : " + th1.isInterrupted()); //현재 상태인 true 반환//static boolean interrupted() : 현재 상태를 반환 후, false로 변경, 누가 interrupted를 호출했는지 알 수 있음
}
ShareData implements Runnable - 공유 자원으로 사용할 데이터 클래스
<java />
package _api.thread.multithread;
//자원을 가지고 연산을 하는 스레드에 사용할 클래스publicclassShareDataimplementsRunnable{
//연산 결과를 저장할 속성privateint result;
//연산에 사용할 인덱스privateint idx;
//result 의 getter 메서드publicintgetResult(){
return result;
}
@Overridepublicvoidrun(){
try{
for(int i = 0; i < 10; i++) {
// synchronized가 없으면 th1, th2가 있을 때 이상한 결과가 출력됨synchronized(this) {
idx++;
Thread.sleep(10); //없거나 너무 작으면 0,1같은 작은 숫자가 나옴
result = result + idx;
}
}
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
MutexMain - 공유 자원을 사용하는 실행 클래스
<java />
package _api.thread.multithread;
publicclassMutexMain{
publicstaticvoidmain(String[] args){
//Runnable 인터페이스로부터 상속받은 클래스
ShareData shareData = new ShareData();
//스레드 생성
Thread th1 = new Thread(shareData);
th1.start();
//하나 했을때는 제대로 결과가 나왔는데 두개하니 잘못된 결과가 나옴//for문이 두 개 있는 것과 같음
Thread th2 = new Thread(shareData);
th2.start();
try{
//30초 대기 - 앞의 작업이 스데르도 동작하기 때문에 작업이 끝날때까지 대기하고 결과를 출력
Thread.sleep(3000);
System.out.println(shareData.getResult());
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
(synchronized를 안하고) 실행을 하면 이상한 결과가 출력
ShareData 인스턴스를 1개만 만들어서 공유하는데 하나의 작업이 완료되기 전에 다른 스레드가 공유 자원을 수정하기 때문에 발생하는 문제
해결방법 한번에 실행되어야 하는 코드를 가진 영역을 찾고 메서드에 synchronized를 붙이던가 코드 영역을 synchronized(공유객체)()로 묶어주면 됨 이렇게 묶어주면 묶인 영역의 코드는 동시에 수정할 수 없음 되도록이면 synchronized 메서드를 만드는 것보다는 블럭을 만드는 것을 권장함 메서드를 묶게 되면 영역이 커져서 공유도가 떨어지기 때문
ShareData 클래스를 수정해서 다시 실행
최근에는ReentrantLock이라는 클래스 이용을권장
인스턴스를 생성 (구현받는 클래스에) lock 메서드로 공유 영역을 만듦 unlock 메서드로 공유 영역 해제
ShareData2 implements Runnable - ReentrantLock 이라는 클래스를 이용
<java />
package _api.thread.multithread;
import java.util.concurrent.locks.ReentrantLock;
//자원을 가지고 연산을 하는 스레드에 사용할 클래스publicclassShareData2implementsRunnable{
//연산 결과를 저장할 속성privateint result;
//연산에 사용할 인덱스privateint idx;
//공유 코드 영역을 설정하기 위한 객체staticfinal ReentrantLock Lock = new ReentrantLock();
//result 의 getter 메서드publicintgetResult(){
return result;
}
@Overridepublicvoidrun(){
try{
for(int i = 0; i < 10; i++) {
//자물쇠를 채워서 unlock 을 만날때까지는 이 영역의 자원을 수정할 수 없음
Lock.lock();
idx++;
Thread.sleep(10); //없거나 너무 작으면 0,1같은 작은 숫자가 나옴
result = result + idx;
Lock.unlock();
}
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
package _api.thread;
publicclassConsumerextendsThread{
public Product myList;
publicConsumer(Product myList){
this.myList = myList;
}
@Overridepublicvoidrun(){
for(int i = 0; i < 26; i++) {
myList.get();
}
}
}
Main 클래스
<java />
package _api.thread;
publicclassConsumerMain{
publicstaticvoidmain(String[] args){
Product prd = new Product();
new Producer(prd).start();
new Consumer(prd).start();
}
}
실행을 하다보면 예외 발생 소비자 스레드인 Customer 스레드가 리스트에 아무런 데이터가 없는데 꺼내려고 해서 발생하는 문제 소비자 스레드의 메서드는 데이터가 없을 때는 대기하고 (wait) 생산자 스레드는 데이터를 만들어 낸 경우 데이터가 만들어졌다고 알려줘야 함 (notify) 기다리기 위해서 호출하는 메서드는 wait 알려주는 메서드는 notify, notifyAll 메서드 이 메서드들은 Object 클래스의 메서드이고,synchronized 메서드 안에서만 동작
일반적으로 개발자들은 클래스의 인스턴스를 1개만 생성할 때는 변수의 이름을 클래스 이름과 동일하게 만들고 첫글자만 소문자로 변경 String string = "apple";
어떤 클래스로부터상속받는 클래스를 1개만 만들 때는클래스 이름 뒤에 Ex를 붙임 Thread 클래스로부터 상속받는 클래스 class ThreadEx extends Thread {
}
인터페이스를 구현한 클래스를 1개만 만들 때는클래스 이름 뒤에 impl을 붙임 Runnable 인터페이스를 구현한 클래스 class RunnableI(i)mpl implements Runnable{
}
extends 하거나 implements 했을 때 변수의 이름은 상위 클래스 이름이나 인터페이스 이름을 사용 ArrayList<String> list = new ArrayList<>(); ▶ List<String> list = new ArrayList<>(); ThreadEx th = new TreadEx(); ▶ Thread th = new TreadEx();