스레드

  • Thread = ‘lightweight Process’ = 일꾼

  • Process 안에서 두 개 이상의 스레드가 돌아가는 것. 멀티 Thread.

    • 두 개의 로직이 같이 실행되거나 이전 로직이 실행되어야 다음 로직이 실행되는 경우, 더 효율성을 증가시키기 위해
    • JVM이 스케줄링을 아주 빠르게 하기 때문에 우리는 동시에 하게 된다고 느낌
    • 스레드는 리소스 소모가 적고 빠르다.
  • java.lang.Thread(final이 없어서 상속도 되고, 추상화되어있지 않아서 반드시 재정의해야 되는 건 없다.)

    • Thread 클래스를 상속받는 방법(Thread(부모) > MyThread(자식))

      • 필수적으로 run() 메소드를 재정의해야됨. 스레드로 동작하고싶은 실행 코드를 run()에 구현.

      • 내가 일꾼을 만들어 내고 그 일꾼이 뭐 할지를 내가 직접 만들고 선택

      • 즉 일꾼이 있기 때문에 start(), sleep(), yield()등의 메소드를 갖고 있음.

      • MyThread t = new MyThread();
        t.start(); // 일을 위한 준비.
        
    • Runnable 인터페이스를 구현하는 방법(Runnable > MyRunnable(구현))

      • run() 재정의해야됨.

      • 강제적으로 구현한 클래스를

      • 일거리와 일꾼이 분리되어 있음. 일거리만 만드는 일.

      • 만약 run이 없다면 Thread 클래스의 원래 있던 run()을 실행하게 됨. 거기에는 target.run();으로 정의되어 있다. 그래서 Runnable target = ?? (MyRunnable) ;으로 만들 수 있다.

      • MyRunnable r = new MyRunnable();
        // r.start(); 이 메소드는 안됨. 일거리만 있기때문에. 반드시 일꾼을 만들어야 함
        Thread t= new Thread(r); // 일꾼한테 하는 일을 주는 것. 일거리는 일꾼을 생성할 때 밖에 못 준다.
        t.start();
        
  • 어떻게 유연성이 있는지 생각해보기. 바뀌지 않는 것과 바뀌는 것의 분리.

PDF 12

  • 힙은 함께 쓰면서 메인스레드 스택 옆에 t1 스레드 스택이 생겨서 여기에는 run()이 쌓인다.
  • 힙과 자원을 공유하기 때문에 동기화 문제가 생긴다.
  • 힙은 1개고 스레다마다 스택이 생긴다.
  • yield() 내가 양보하고 싶다고 말하는 거. 하지만 yield()가 아니더라도 스케줄링에 따라 그냥 비선점 상태가 될 수 있다. 즉, 선점을 당해서 일하고 있다가 비선점 상태가 되면 RUNNABLE 상태가 되서 기다린다. 그래서 PDF 18에서 RUNNING에서 RUNNABLE로 가는 선이 하나 더 있어야 한다.
  • 한 번 종료된 스레드는 다시 재사용할 수 없다. 다시 start()를 못 하고 new Therad.start()로 새로 하나 더 만들어야 한다.
  • sleep() : 다른 스레드에게 작업권을 양보하는 것. 바로 자러 감. waiting 상태가 됨. 시간을 줘서 그 시간동안 대기 풀(NOT RUNNABLE 상태)에 있음. 그 다음에 스케줄러가 pick을 해줘야 RUNNABLE 상태로 감.
  • I/O 블록킹 : 입력받는 얘들은 입력 받기 전까지 계속 기다려야하니까 대기 풀로 들어감.

동기화

  • 힙을 함께 쓰고 공유함. 스택은 따로따로.
  • 메일 전송같은 경우, 메일 전송 스레드(큐에 있는 메세지를 계속 돌면서 전송)와 멤버 스레드(가입하고 메일 전송 코드를 큐에다 넣어줌)가 따로 있음. 두 스레드가 같은 queue를 바라봐야 함. -> 자원 공유로 인한 문제
  • synchronized 메소드 앞(선언부)에 붙일 수 있다. -> 화장실이 아니라 화장실 가는 길을 다 막는다. 즉, 코드 줄이 아니라 메소드 전체를 synchronized한다.
    • 비효율적이다. 다른 코드는 동기화 안 해도 괜찮은데.
    • synchronized을 블럭으로 걸 수 있다. synchronized()의 괄호 안의 매개변수에 그 객체의 레퍼런스를 명시해야 된다.
    • 자기 자신에게 걸고 싶다면 this.
    • 동기화 안 된 얘들은 웨이트 풀로 뺀다.
  • 상대가 해주길 기다리면서 서로 놓아주지 않는 상태 = 교착상태
    • 멤버 스레드는 열쇠가 없어서 메일스레드로부터 열쇠가 돌아오기만을 기다림. 메일 스레드는 열쇠를 갖고 멤버 스레드가 큐에 넣기를 기다림. 반대라도 교착상태다.
    • 선행처리해야 될 얘가 생김. notify로 계속 알려줘야 됨. 안 그러면 웨이트 풀에 있는 얘들이 깨어나지 못함. 왜냐면
  • 스레드의 종료. 자원이 제대로 해제되지 않을 수 있기 때문에 deprecated 되어있다.
    • run() {} 안에 while(flag)를 주어서 한 번 돌면 flag를 false 로 만들어 그만 돌게 할 수 있다.
  • Stringbuilder. 싱글 스레딩 환경에서 동기화처리를 하면 오버헤드가 걸리는데 StringBuilder는 동기화 처리가 되어있지 않아서 싱글 스레딩 환경에서 좋다.
  • 멀티스레딩 환경에서는 Stringbuffer를 쓰는 게 낫다.

++

채팅 - 스레드?

채팅이 완성되려면 서버도 스레드가 들어가야 하고, 클라이언트도 스레드가 들어가야 한다.

소켓은 인풋, 아웃풋으로 두 개가 있다.

사용자가 서버로 가는 스트림은 OutuputStream. 이게 서버에서는 InputStream.

그래서 서로가 Source와 sync(목적지)가 된다.

필터스트림을 써서 간소화하자.

  • 동기방식 - 선행코드 끝나면 후행코드로 가고…
  • 비동기방식 - 콜백. 상황을 알려서 흐름을 분기시키는 작업을 해야된다. 하지 않으면 모름. 스레드를 파생시키는 것도 비동기방식.

어디에 쓰레드를 써야할까?

리턴으로 받는 즉, 어떤 코드가 끝나야 실행되는 방식으로는 스레드로 하면 안 됨. 기다려야하니까.

pm.send()하고 흐름을 분기시켜서 pm.close()도 스레드로 짠다. 그래야 서버로 데이터를 전송하면서 다시 while문으로 가능하니까. 예를 들어서 이렇게 짜지 않으면, UI로 짰을 때, 전송 버튼을 누르고 누른 상태로 계속 가만히 있어야 한다. 그래서 이렇게 흐름을 분기시키지 않으면 자동적으로 에러가 나도록 강제해놨음.

전송에 실패했다면 기다려야된다. 왜냐면 전송에 실패했는데 아예 다음 걸로 넘어가면 안 되니까.

민형이 코드 : 보낼 때는 for문으로, 받을 때는 in.readObject 두 번

UI

컴포넌트 : 재사용가능한 모듈, 단위

  • ex) 버튼, 창 등등… 모두 컴포넌트

CBD : Component Based Develop 컴포넌트 빌드 디벨롭. 컴포넌트 중심적인 개발.

자바가 지원하는 UI

  • AWT(java.awt) - 우리가 할 것.
    • 속도가 빠르고 리소스적인 소모가 많다. 한정적이다. 운영체제가 제공해주는 것들을 연결해주는 가교역할. 운영체제가 지원해주지 않는 컴포넌트는 못 쓴다.
  • Swing(javax.swing)
    • 속도가 느리지만 awt에 의존하는 것이 있다. awt위에 스윙이 있음. 컴포넌트들이 많다.
  • Component(최상위 타입)
    • Container(다른 컴포넌트를 자신 안에 포함시킬 수 있는 컴포넌트. 공간, 영역의 느낌, 집 공간 그 자체, 바탕 공간이 되어질 수 있음) - 컨테이너별로 하나의 배치관리자밖에 못 씀.
      • Window(집, 일단 창을 하나 띄워야함)
        • Frame - 사용자에게 전달하고 싶은 정보들…
        • Dialog - 독자적인 창, 부가적인 정보
      • Panel(방) - 엄청 쓰게 됨. 내가 원하는 배치를 할 수 있도록 패널을 쪼개서 조립하는 느낌으로 할 수 있어서.
    • nonContainer()
      • Button
  • LayoutManager - 배치관리자
    • FlowLayout(Panel을 썼을 때 기본 배치관리자) - 가로 배치 ==>왼쪽에서 오른쪽으로 컴포넌트를 배치할 수 있음, 또는 세로 배치 ==> 위에서 아래로 배치. 컴포넌트 크기가 고정
    • BorderLayout(Window로 썼을 때 기본 배치관리자) - 위치 고정, 컴포넌트 크기는 가변. 5개(East, Center, West, South, North)로 나눠서 컴포넌트가 1개씩만 배치가 가능. 그래서 많은 걸 추가하고 싶을 때 Panel에 추가하고 추가하면 됨. 크기를 늘려도 위치는 그대로 유지가 된다.
    • GridLayout - 격자형태 배치. 표라고 생각하면 됨. 칸 형태로 1, 2, 3, 4, 5, 6형태로 배치된다. 위치는 고정,

어떤 컴포넌트가

약속을 위한 인터페이스를 설계해놓은 것.

멀티 스레드

운영체제에서 실행중인 하나의 프로그램(app)을 프로세스라고 부릅니다. 예를 들어, Chrome 브라우저를 두 개 실행했다면 두 개의 chrome 프로세스가 생성된 것입니다.

메신저같은 경우, 채팅 기능을 제공하면서 동시에 파일 전송 기능을 수행하기도 합니다. 이를 ‘멀티 태스킹’이라고 두 가지 이상의 작업을 동시에 처리하는 것을 말합니다. 이렇게 처리할 수 있는 이유는 ‘멀티 스레드’를 이용하는 것입니다.

멀티 프로세스들은 OS에서 할당받은 자신의 메모리를 받아서 실행하기 때문에 독립적이지만 멀티 스레드같은 경우는 하나의 프로세스 안에서 실행되기 때문에 다른 스레드에게 영향을 미칩니다.

모든 자바 app들은 메인 스레드가 main() 메소드를 실행하면서 시작됩니다. 순차적으로 실행하다가 마지막 코드를 실행하거나 return문을 만나면 종료됩니다. 메인 스레드는 필요에 따라 작업 스레드를 만들어서 병렬로(멀티 스레드로) 코드를 실행할 수 있습니다. 그래서 멀티 스레드에서는 실행중인 스레드가 하나라도 있다면 종료되지 않습니다.

Thread 클래스로부터 직접 생성

java.lang.Thread 클래스로부터 작업 스레드 객체를 직접 생성하려면 다음과 같이 Runnable을 매개 값으로 갖는 생성자를 호출해야 한다. Thread thread = new Thread(Runnable target); Runnable은 작업스레드가 실행할 수 있는 코드를 가지고 있는 객체다. 인터페이스 타입이기 때문에 구현 객체를 만들어야 한다. Runnable에는 run() 메소드가 하나 정의되어 있는데, 구현 클래스는 run() 을 재정의해서 작업 스레드가 실행할 코드를 작성해야 한다.

class Task implements Runnable{
	public void run(){
	}
}

Runnable은 작업 내용을 가지고 있는 객체이지 실제 스레드는 아니다. Runnable 구현 객체를 생성한 후, 이것을 매개 값으로 해서 Thread 생성자를 호출하면 비로소 작업 스레드가 생성된다.

reference