쓰레드

Jung·2021년 2월 25일
0

수업

목록 보기
2/20

무언가를 정의할 때 유용한 방법:
A는 B의 하위 클래스인데
(사람은 동물인데)
다른B의 종류들과 차이점은 ~~ 가 있습니다.

쓰레드는 최소 지정 단위가 메소드이다. 15라인을 실행해달라는 쓰레드는 없다. 쓰레드는 메소드 단위로 실행 흐름을 제어할 수 있다.
쓰레드를 만드는 방법은 두가지가 있다.
1) Runnable 인터페이스를 구현해서
2) Thread를 상속해서

Runnable은 함수형 인터페이스이다. 함수형 인터페이스보다 Runnable보다 먼저 나왔는데, 생각해보니 매개변수 없고 리턴타입 void이니 함수형 인터페이스로 쓸 수 있겠구나 해서 그렇게 쓴다.

새 쓰레드를 생성하는 방법은,
(추가적으로 프로그램을 실행하면 main 메소드를 실행하며 main Thread가 생긴다.) new Thread로 만들 수 있다. 끝이다.
new Thread의 start()를 호출해야 쓰레드가 만들어진다.

public class Task extends Thread{

@Override
public void run(){
 System.out.println("I am THread");
}

public static void main(String [] args){
	System.out.println("Main start");
	Thread t = new Task();
	t.start();
	System.out.println("Main thread end")
}

Main 프로세스가 하나 있다. t까지 쓰레드 총 두개이다.
메모리 구간엔 메소드 에어리어, 스택, 힙, 쓰레드.
쓰레드를 실행하면 쓰레드가 OS를 거쳐 CPU의 코어와 연결된다.

t.run();를 하면 실행은 똑같지만, 쓰레드가 한개뿐이다. 왜냐하면 단지 run()메소드가 호출된 것 뿐이다.
쓰레드가 두개면 스택이 두개 생긴다. start()를 하면 main이 실행되는데 main에서 start()를 호출하는 순간 t1쓰레드가 하나 생긴다. 이 상태에서 run()이 바로 실행되지는 않고, 조금 후에 runnable상태가 되어서 run()이 실행된다.
반면 run()을 호출하면, main쓰레드에서 main()메서드가 호출되고 그 위에 run()메서드가 호출된다.

public class Task extends Thread{

@Override
public void run(){
this.setName("Jane");
 System.out.println("I am Thread");
 System.out.println(this.getName+ this.getId());
}

public static void main(String [] args){
	Thread t = Thread.currentThread();
    System.out.printf("Main is (%s) (%d) start", t.getName(), t.getID());
    
	System.out.println("Main start");
	Thread t = new Task();
	t.start();
	System.out.println("Main thread end")
}

결과: I am Thread Jane
main메서드에서 getName못하고,
Thread t = Thread.currentThread();해야함.

jvm에 ?? 우리 프로세스 안에 다른 쓰레도도 있기 때문에 main쓰레드의 getName은 main, getId는 1이다. Jane쓰레드는 18번. 숫자 차이가 많이 난다.
우리가 만들지 않아도 main쓰레드, gc 등 여러 쓰레드가 생긴다. 우리가 원하는 새로운 쓰레드는 new Thread로만들어진다.

자바 프로그램이 죽는 것은 모든 일반 쓰레드가 죽는 때이다. (C에선 main이 죽으면 나머지도 다 죽음) 자바에선 main이 리턴되어도 프로그램이 끝나지 않았을수도 있다.

public class Task extends Thread{

@Override
public void run(){
this.setName("Jane");
 System.out.println("I am Thread");
 System.out.println(this.getName+ this.getId());
 while(true){
 	try{
    	
        count ++;
        Thread.sleep(10000);
        System.out.println(count);
        }catch(InterruptedException e){
        	e.printStrackTrace();
        }
      
    }
}

public static void main(String [] args){
	Thread t = Thread.currentThread();
    System.out.printf("Main is (%s) (%d) start", t.getName(), t.getID());
    
	System.out.println("Main start");
	Thread t = new Task();
	t.start();
	System.out.println("Main thread end")
}

ㄴ 메인 쓰레도 끝나도 다른 쓰레드가 while(true)무한 루프 돌며 안끝난다.

쓰레드 중 Daemon쓰레드가 있다. 쓰레드이름.setDaemon(true);해주면 데몬이 된다.
main이 끝난 후에도 다른 쓰레드 계속 돌아가면 안 끝나는 것이다.
다만, 데몬쓰레드는 남을 돕기 위한 쓰레드기 때문에 데몬이 돕는 모든 일반 쓰레드가 종료되면 같이 종료된다.

??필드: 클래스 안 메서드 밖 인스턴스, 스태틱 변수 쓰는 곳??

쓰레드 1000개 만들려면 쓰레드 리스트를 만들면 된다. 또는 배열을 써도 되는데 리스트가 선호된다고 한다..

public class Task extends Thread{
	public static final int N = 200_000;
    private int customId;
    private int size;
    
    public Task(int customId, int size) {
    	this.customId = customId;
        this.size = size;
    }
    
    public void run() {
    	for(int i =0; i< size; i++) {
        if(customId % 2 == 0) {
        	System.out.println("짝짝짝")
        } else{
        	System.out.println("홀홀홀")
        	}
        }
    }

public static void main(String [] args){
  final int numThread = 2;
  Thread[] t new Thread[numThread];
  
  for(int i =0; i< numThread; i++) {
  	t[i] = new Task();
  }

  long start = System.currentTimeMillis();
    for(int i =0; i< numThread; i++) {
        t[i].run();
      }
  long end = System.currentTimeMillis();
  System.out.println(end-start);

이렇게 하면 순차 실행된다.
쓰레드 하나일떄(numThread ==1) 2.1초, 쓰레드 두개일때 3.7초 걸린다. run일때는 쓰레드가 만들어지지 않기 떄문에 그렇다.
t[i].run(); -> t[i].start()로 하면 쓰레드가 여러개 생긴다.
그러면 main이 먼저 끝나기 때문에 시 end-start가 더 먼저 끝나서 시간이 짧아진것처럼 보인다.

이럴때 main이 다른 쓰레드들을 기다리도록 하기 위해선 join을 해주면 된다.

public class Task extends Thread{
	public static final int N = 200_000;
    private int customId;
    private int size;
    
    public Task(int customId, int size) {
    	this.customId = customId;
        this.size = size;
    }
    
    public void run() {
    	for(int i =0; i< size; i++) {
        if(customId % 2 == 0) {
        	System.out.println("짝짝짝")
        } else{
        	System.out.println("홀홀홀")
        	}
        }
    }

public static void main(String [] args){
  final int numThread = 2;
  Thread[] t new Thread[numThread];
  
  for(int i =0; i< numThread; i++) {
  	t[i] = new Task();
  }

  long start = System.currentTimeMillis();
    for(int i =0; i< numThread; i++) {
        t[i].start();
      }
      for(int i =0; i< numThread; i++) {
        t[i].join();
      }
  long end = System.currentTimeMillis();
  System.out.println(end-start);

join이란? 쓰레드가 실행되다가
쓰레드란, 현재 쓰레드에서 갈라진 느낌이다. join은 실행되던 애가, 다른 친구 쓰레드를 자기한테 합류시켜달라는 것이다. 그러면 다른 애들 끝날때까지 기다리게 된다. join시킨 해당 애들이 끝날떄까지 나는 아무것도 안하겠다는 것이다.
println은 IO로 출력한다. standard Output으로 출력하는데, 이것은 한개뿐이다. 쓰레드가 한개일땐, 한명이 하나의 장비(출력장비)를 사용한다. 쓰레드가 여러개면, 여러명이 하나의 장비를 사용한다. 그렇기 때문에 오히려 쓰레드 여러개인데도 시간이 더 느려지는 것이다. 그렇기 때문에 쓰레드 여러개로System.out.println빨라질 것이라 생각하면 안된다. run을 수정하면 쓰레드 여러개일때 더 빠를 것이다.

InterruptedException은 반드시 캐치해줘야한다. sleep하고 있는 와중에 interrupt를 해준다.

public class Task extends Thread{
	public static final int N = 200_000;
    private int customId;
    private int size;
    
    public Task(int customId, int size) {
    	this.customId = customId;
        this.size = size;
    }
    
    public void run() {
    	long sum =0;
    	for(int i =0; i< size; i++) {
        	sum += 1;

        }
       System.out.println("thread ended " + getName());
    }

public static void main(String [] args){
  final int n = 4000_000_000;
  final int numThread = 2;
  Thread[] t new Thread[numThread];
  
  for(int i =0; i< numThread; i++) {
  	t[i] = new Task(i, n);
  }

  long start = System.currentTimeMillis();
    for(int i =0; i< numThread; i++) {
        t[i].run();
      }
      for(int i =0; i< numThread; i++) {
        t[i].join();
      }
  long end = System.currentTimeMillis();
  System.out.println(end-start);

2초 걸림.
run을 start로 바꾸면?
시간이 줄어든다. 쓰레드가 병렬로 실행되기 때문이다.
쓰레드 아무리 많아도 IO 작업은 분산이 안된다. OS 운영체제가 시스템 콜이란 것으로 IO를 대신해주기 때문이다. 심부름 센터같은 것이다. 쓰레드 아무리 많아도 빨라지지 않는다.

public class Task extends Thread{
	public static final int N = 200_000;
    private long start;
    private long end;
    public static long result;
    
    public Task(long start, long end) {
    	this.start = start;
        this.end = end;
    }
    
    public void run() {
    	for(long i = start ; i< end; i++) {
        	result += 1;
        }
       System.out.println("thread ended " + getName());
    }

public static void main(String [] args){
  final int n = 1000_000_000;
  final int numThread = 2;
  final long size = n / numThread;
  Thread[] t new Thread[numThread];
 
  
  for(int i =0; i<= numThread; i++) {
  	t[i] = new Task(i*size+1, (i+1)*size);
  }

  long start = System.currentTimeMillis();
    for(int i =0; i< numThread; i++) {
        t[i].run();
      }
      for(int i =0; i< numThread; i++) {
        t[i].join();
      }
  long end = System.currentTimeMillis();
  
  System.out.println("time elapsed: + (end-start));
   System.out.println("sum of 1 to %d is %d\n", n, Task.result);

run- > start로 바꾸면, 멀티쓰레드이므로 빨라질 것이다. 아까와는 달리 IO자원도 없으므로 .

그런데 결과가 틀렸다. result가 스태틱으로 서로 공유하므로 두 쓰ㅡ레드 다 읽을 떄는 1000인데도 동시에 사용하기 때문이다.
똑같은 result 150에 대해 쓰레드 1은 50을 더하고 쓰레드2는 100을 더하면, 정환한 값은 300이 되어야하는데, 재수가 없으면 150, 200 중 하나가 된다. 나중에 더한 쓰레드 값이 반영된다.

그래서 한명이 작업 완료할 때까지 다른 사람이 못들어오도록 락을 걸어야 한다. synchronized로 메서드 분리해줘야 한다. sum메서드를 synchronized로 만들고, run()에서는 sum()을 호출하도록만 하면 된다.

public class Task extends Thread{
	public static final int N = 200_000;
    private long start;
    private long end;
    public static long result;
    
    public Task(long start, long end) {
    	this.start = start;
        this.end = end;
    }
    
    public synchronized void sum() {
        for(long i = start ; i< end; i++) {
        	result += 1;
        }
       System.out.println("thread ended " + getName());
    }
    
    public void run() {
		sum();
    }

public static void main(String [] args){
  final int n = 1000_000_000;
  final int numThread = 2;
  final long size = n / numThread;
  Thread[] t new Thread[numThread];
 
  
  for(int i =0; i<= numThread; i++) {
  	t[i] = new Task(i*size+1, (i+1)*size);
  }

  long start = System.currentTimeMillis();
    for(int i =0; i< numThread; i++) {
        t[i].run();
      }
      for(int i =0; i< numThread; i++) {
        t[i].join();
      }
  long end = System.currentTimeMillis();
  
  System.out.println("time elapsed: + (end-start));
   System.out.println("sum of 1 to %d is %d\n", n, Task.result);

그런데 result는 프리미티브라서 안된다. 싱크로나이즈에는 참조??에만 걸 수가 있는데.


class Result{
	public long sum; 
}

public class Task extends Thread{
	public static final int N = 200_000;
    private long start;
    private long end;
    public Result result;
    
    public Task(long start, long end, Result r) {
    	this.start = start;
        this.end = end;
        this.result = r;
    }
    
public void sum() {
    for(long i = start ; i< end; i++) {
    	synchronized (result) {
        result += 1;
        }
    }
   System.out.println("thread ended " + getName());
}

public void run() {
	sum();
}

public static void main(String [] args){
  final int n = 1000_000_000;
  final int numThread = 2;
  final long size = n / numThread;
  Thread[] t new Thread[numThread];
 
 
Result r = new Result();
r.sum =0;
  
  for(int i =0; i<= numThread; i++) {
  	t[i] = new Task(i*size+1, (i+1)*size);
  }

  long start = System.currentTimeMillis();
    for(int i =0; i< numThread; i++) {
        t[i].start();
      }
   for(int i =0; i< numThread; i++) {
        t[i].join();
      }
  long end = System.currentTimeMillis();
  
  System.out.println("time elapsed: + (end-start));
   System.out.println("sum of 1 to %d is %d\n", n, r.sum);

synchronized는 클래스에만 먹고, 프리미티브에도 안되고, 래퍼 클래스도 안된다. Result클래스를 추가하면 참조라 싱크로나이즈 가능하고, 정확한 결과값이 나온다.
그런데 start() 보다 run()이 더 빠르다. run()은 자물쇠를 한번쥐면 끝까지 쥔다. lock overhead가 발생하지 않는다. 즉 쓰레드를 제대로 못 쓰고 있다는 말이다. 쓰레드 다섯개가 하나보다 못한 이유는 동기화 오버헤드이다. 코딩을 잘못한 것이다.

빨리 하는 방법은 동기화 최소화를 하는 것이다.
result결과값을 더해주는 것이다.
sum()메서드 for문 안에 localSUm += i;해주고
synchronize 블럭 안에 result.sum += localSum;
이렇게 하면 지역변수 하나 더해준다음에 락을 마지막에 한다.
지금은

    public void sum() {
        for(long i = start ; i< end; i++) {
        long localSum =0;
        	synchronized (result) {
            localSum += i;
            }
            result = localSum;
        }
       System.out.println("thread ended " + getName());
    }
    
    public void run() {
		sum();
    }

synchronize메서드를 Result클래스 안에 넣어도 된다.


wait: n초 기다렸다가 runnable상태 된다. wait는 호출하는 순간 슬립이 된다(???) wait는 notify로 깨운다. (wait는 synch된 상태에서만)

notify: wait하던게 notify 받으면 바로 runnable상태 된다. 쉬고 있다가 깨워진다. notify()는 나 때문에 대기하는(wait) 애들..? 중 한명을 깨운다. notifyAll()은 나를 대기하는 애들 전체를 깨운다. 얘는 싱크로나이즈 블록이 마음에 안들때 사용한다. 원래는 jvm이 관리해주는 싱크로나이즈 메서드 등의 기능? 이 마음에 안들때..? 쓴다. 우리가 쓸일은없고 프레임워크 개발자, 게임 개발자들이 쓴다.
게임은 항상 락이 걸린다. 게임에 흡혈 스킬이면 남의 hp 빨아온다. 두명이 동시에 서로 흡혈하는데 흡혈이 synchronize 메서드라 서로 기다려야하는 상황이면, deadlock걸려서 게임이 죽는다. 이런 상황에서 deadlock을 꺠려면? deadlock의 조건 3. 1) non pre..?? 메서드가 서로 방해되지 않는 상황. interrupt 안되는 상황. 2) circular waiting: 서로 고리 걸면 3) mutual exclusion:
일반적으로 두번쨰 조건을 끊는 방식으로 데드락을 해결한다. 동시에 시작했을 때 아무나 한명을 해시코드 값을 비교해 빠른쪽 값을 끊는다.

sleep: 특정 시간동안 잠든다.

join: 기다려준다.

쓰레드는 this.sleep도 안된다. Thread.sleep()해야한다.
Thread.sleep하면 누가 슬립하는걸까? 현재 실행중인 쓰레드이다.

실행 중인 애가 sleep하므로, run()안에서 sleep하면 그 쓰레드가 잠드는 것이다. main쓰레드 안에서 Thread.sleep()하면 메인쓰레드가 슬립하는 것이다.
스태틱 변수 Thread이고, t.sleep()는 말이 안된다. t.sleep 하고 특정 쓰렏 t를 sleep시킬방법이 없다. sleep이 호출되는 실행되는 쓰레드가 t가 자는지 꺠어있는지 알수가 없다.

interrupt: 내가 하는 일보다 우선순위가 높아서 빨리 처리해야하는 경우이다. sleep중인 쓰레드를 방해하거나 돌아가고 있으면 방해한다.
잠든 쓰레드를 interrupt하면, InterruptedException이 발생한다. 이 예외를 catch해야하고 throw하면 안된다.

isInterrupted(): interrupt()메서드로 인터럽트되면 true/false가 바뀐다.

yield: 양보하다. yield를 호출하면 하던 일 중단하고 그만두고 대기줄로 돌아간다. 다른 쓰레드에게 양보하는 것이다. 대기줄로 돌아가면 다시 언제 실행될지 모르고, 대기줄의 쓰레드 중 누가 실행될지는 OS운영체제가 결정한다. 동기화 성능을 극대화할때 사용한다.
그렇지만 멀티쓰레드 코딩은 할일이 별로 없다. 프레임워크가 제공하기 때문이다.
yield()하면 대기줄에 기다리는데, 기다리다 실행되면 yield() 다음 줄부터 실행된다. sleep이 아니기 때문에, InterruptedException도 발생할 일 없다.

디비 네트워크 운영체제 자료구조 순으로 공부하기. 우선순위가 이렇다. 네트워크 모르면 천대당하지만 스프링이 알아서 잘해준다.
디비는 디비개론과 mySql공부하기
OS는 운영체제 이론과 리눅스 공부하기

0개의 댓글