[TIL] 코드스테이츠 SEB BE Day 18

💡 Today I Will Learn

  • Stream - Pair
  • 파일 입출력
  • Thread
  • JVM

✏️ Summary


파일입출력 (I/O)


입출력을 위한 InputStream, OutputStream 그리고 프로세스를 위한 PipedInputStream, PipedOutputStream 등이 있다.

스트림은 단방향 데이터 전송이기에 입출력 모두 각각의 스트림이 필요하다.

  • FileInputStream
try{
  FileInputStream input = new FileInputStream("text.txt");
  BufferedInputStream buffer = new BufferedInputStreama(input);

  int i=0;
  while((i = buffer.read()) != -1){
    // read로 읽고 i가 -1인지 확인
    System.out.print((char) i);
  }

  input.close();
}catch(Exception e){
  System.out.println(e);
}

FileInputStream을 BufferedInputStream에 담아 사용하면 성능이 향상된다. 많은 양의 데이터 입출력에 도움을 준다.

  • FileOutputStream

try{
  FileOutputStream output = new FileInputStream("text.txt");
  String word="text";

  byte b[] = word.getBytes();
  output.wirte(b);
  output.close();
}catch(Exception e){
  System.out.println(e);
}

해당 디렉토리 내에 text.txt 파일이 생성된다.

FileReader, FileWriter


File I/O Stream은 바이트 기반 스트림으로 1byte 기준이지만 Java의 Char타입은 2byte이다. 따라서 개선책으로 해당 문자기반 스트림이 등장하였다.

FileReader는 인코딩을 유니코드로 변환하고, FileWriter는 유니코드를 인코딩으로 변환한다.

  • FileReader
  String fileName = "text.txt";
  FileReader file = new FileReader(fileName);
  BufferedReader buffer = new BufferedReader(file);

  int i=0;
  while((i = buffer.read()) != -1){
    // read로 읽고 i가 -1인지 확인
    System.out.print((char) i);
  }

  input.close();

}catch(Exception e){
  System.out.println(e);
}
  • FileWriter

try{
  String file = "text.txt";
  FileWriter output = new FileWriter(file);

  String append = "code!!!";
  output.wirte(append);
  output.close();
}catch(Exception e){
  System.out.println(e);
}

FileInputStream과 유사하게 BufferedReader를 이용하면 성능을 개선할 수 있다.

File


파일과 디렉터리에 접근하는 클래스이다.

메서드설명
String getPath()파일의 경로를 반환한다.
String getParent()상위 폴더의 경로를 반환한다.
String getCanonicalPath()절대경로 중 완전한 절대경로를 반환한다.
boolean canWrite()수정 가능한 지 반환한다.
boolean createNewFile(path, fileName)파일을 생성한다.
boolean renameTo(new File(path, fileName))파일명을 변경한다.

Thread


스레드는 한 코드의 흐름이다.

  • Multi-Thread

두가지 작업을 동시에 처리하는 것이다. OS에서 CPU와 메모리를 프로세스마다 할당하고, 병렬로 실행한다.

멀티 프로세스가 어플리케이션 단위의 멀티 태스킹이면, 멀티 스레드는 어플리케이션 내부에서의 멀티 태스킹이다.

데이터 처리시간을 줄이기 위해 데이터를 나누어서 병렬처리하거나, 어플리케이션에서 네트워크 통신을 하기위해 사용된다. 또는

  • Main-Thread

Java에서는 메인스레드에서 main() 함수를 실행하면서 시작된다. 메인 스레드는 메인 함수의 첫부분부터 끝까지 실행되거나 return을 만나 종료된다.

메인스레드는 스레드를 만드는 멀티스레드를 통해 멀티태스킹을 수행한다.

싱글 스레드에서는 메인 스레드가 종료하면 프로세스도 종료되지만, 멀티스레드에서는 하나의 스레드가 실행되고 있다면 종료되지 않는다.

메인스레드가 먼저 종료되더라도 프로세스는 종료되지 않는다.

Thread의 생성과 실행


병렬작업이 필요한 만큼 스레드를 생성한다. 스레드 또한 객체로 생성되기에 클래스가 필요하다.

Thread thread = new Thread(Runnable target);

Runnable을 매개변수로 갖는 Thread 객체를 생성해야한다.

Runnable은 스레드가 실행할 수 있는 코드를 가지고 있는 객체라는 의미로 붙여졌다. 인터페이스이므로 run() 메서드를 재정의하여 실행할 코드를 작성해야한다.

Thread thread = new Thread(new Runnable(){
  @Override
  public void run(){
    //실행코드
  }
})

단 하나의 추상메서드만 있으므로 함수형 인터페이스이다. 따라서 Lambda식으로 처리 가능하다.

thread.start();

최종적으로 start() 메서드를 통해 호출해야 실행된다.

  1. Thread 객체생성
  2. Start() 메서드 호출
  3. run() 내에 실행코드 수행(스레드 실행)
  • Thread 하위 클래스부터 생성

Thread 클래스를 상속하여 run()을 재정의하고 스레드에서 실행할 코드를 작성하는 방법도 있다.

// 1. 클래스 상속을 이용
public class extendThread extends Thread{
  @Override
  public void run(){
    // 실행코드
  }
}

// 2. 익명객체로 이용
Thread thread = new extendThread();

Thread thread = new Thread(){
  public void run(){
    // 실행코드
  }
}
  • Thread 인스턴스 얻기
Thread thread = Thread.currentThread();

현재 스레드를 참조하여 getName(), setName()과 같은 메서드를 사용할 수 있다.

동기화 메서드와 동기화 블록


멀티 스레드에서 하나의 스레드만 실행할 수 있는 영역은 임계 영역이다. 임계 영역 지정을 위해 동기화 메서드와 동기화 블록을 사용한다.

스레드가 객체 내부 동기화 메서드나 블록에 들어가면 객체에 잠금을 걸어 다른 스레드가 접근하지 못하도록 해야한다. 동기화 메서드는 synchronized 키워드를 붙인다.

  • 스레드의 상태

start()로 호출하면 스레드는 대기상태가 된다. 스레드 스케줄링으로 선택된 스레드가 CPU를 점유하여 run()을 실행하는 것이다.

실행 중인 스레드는 중간에 실행대기 상태로 돌아갈 수 있다. 이때 대기하던 다른 스레드가 실행 상태로 바뀌는 것이다. 즉, 스레드는 실행과 대기상태를 번갈아가며 조금씩 수행한다.

실행 중인 스레드가 대기상태가 아닌 일시정지 상태로 갈 수 있는데, 이는 실행 불가능한 상태를 의미한다. 이때는 다시 대기상태로 가기위해 getState() 메서드로 상태를 확인해야한다.

Thread.State
NEW
RUNNABLE
WAITING
TIMED_WAITING
BLOCKED
TERMINATED
  • 스레드 상태 제어
메서드설명
interrupt()일시정지 -> 실행대기 or 종료
notify(), notifyAll()wait() 메서드에 의한 일시정지 -> 실행대기
sleep()시간동안 일시정지 상태 -> 실행대기
join()일시정지 -> 실행대기로 가려면 join() 메서드를 가진 스레드 종료(기다렸다가)
wait()일시정지 -> 실행대기 (시간 or notify())
yield우선순위가 동일한 스레드에게 실행을 양보하고 대기상태로 돌아간다

wait(), notify(), notifyAll() 메서드는 번갈아가며 실행할 때 이용하는데, 반드시 동기화 메서드 또는 동기화 블록 내에서만 사용 가능하다.

Thread Pool


무분별한 스레드 증가와 스케줄링으로 인한 성능저하를 방지하기 위해 스레드 풀을 사용한다. 작업에 사용되는 스레드 수의 한계를 두는 것이다.

생성메서드
newCacheThreadPool()
newFixedThreadPool(int num)
종료메서드설명
void shutdown()남아있는 모든작업 처리 후 종료
List shutdownNow()남아있는 작업을 무시하고 종료, 못한작업 반환
boolean awaitTermination(timeout, unit)shutdown() 후 timeout안에 처리하면 true, 아니면 false

JVM


JAVA 이전에는 프로그램이 OS에 독립적이지 못했다.

OS에 독립적이기에 소스코드 하나로 어느 OS에서도 프로그램을 실행시킬 수 있다. OS로의 독립은 JVM에서 구현된다.

“Write Once, Run Anywhere”

프로그램에서 OS에게 자원을 요청하는 방식이 마다 다르기 때문에 Java는 JVM을 사이에 두고 OS와 조율한다.

그러므로 JVM은 각 OS에 적합한 Version이 존재하며, JVM이 각 OS에 맞게 실행시켜준다.

JVM 구조


JVM

  1. 소스코드를 컴파일러가 클래스 파일로 만든다.
  2. 클래스 로더가 클래스 파일을 불러 JVM이 동작한다.
  3. Runtime Data Area에 데이터가 들어가게 된다.
  • Stack & Heap

1. Stack

메서드가 호출되면 Method Frame이 생성되는데 이 공간이 Stack으로 이루어져 있다. 각 메서드 내부에는 사용하는 필드나 여러 정보들이 임시로 저장이 된다.

메서드 재귀 호출을 생각해보면 Stack으로 이루어지는 것이 이해가 간다.

2. Heap

JVM이 동작하면서 자동으로 생성되는 영역으로 객체, 인스턴스 변수, 배열 등이 저장된다.

Stack 영역에 있는 참조변수에 생성한 인스턴스의 주소를 전달하는 일련의 과정이 일어난다.

Garbage Collection


GC란 메모리를 관리하는 프로세스가 포함되어 있으며, 사용하지 않는 객체를 정리하여 메모리 공간을 확보한다.

Heap영역 내에 Old, Young 영역이 나누어져 있다. 이름 그대로 Old는 오래 살아남은 객체이므로 버려지는 것은 적고 (Minor GC), Young은 새롭게 생성되고 삭제가 빈번하게 일어나는 영역(Major GC)이다.

GC가 실행되면 JVM이 어플리케이션을 중단(Stop The World)시키며 사용하지 않는 메모리를 찾아(Mark) 해제하는 작업(Sweep)을 한다.

📌 정리


JVM, IO도 좋았지만, 특히 생소한 개념이던 Java에서의 Thread를 다루어서 조금은? 흥미로웠다.

OS 개념에서만 배웠었던 Thread를 자바에서 다루면서 복습겸 다시 상기할 수 있는 좋은 기회가 된것 같다😍

👿 Problem

Thread 하위 클래스로부터 생성? 무슨차이일까?

👼 Solution

Thread 클래스의 내부를보면 Runnable이 있으면 Runnable 내의 run, 아니면 Thread를 상속받아 오버라이드한 run 또는 익명 클래스로 생성한 run을 실행하게 된다.

🎯 Next Week


  • 기술면접
  • 잡서칭

Back to [TIL] 코드스테이츠 SEB BE Day 17