[JAVA] 스레드, 멀티스레드, 버퍼, 객체 관련 간단 정리

dejeong·2024년 6월 28일
0

JAVA

목록 보기
2/24
post-thumbnail

Scanner, BufferedReader, BufferedWriter, StringBuilder 의 차이점과 사용법 주요 메서드 예시 코드 를 공부하면서 잘모르겠거나 헷갈리는 단어들을 모두 통합하여 정리해보았다.


🤷‍♀️ 스레드의 개념

프로그램 내에서 실행되는 가장 작은 작업 단위. Java에서는 하나의 프로그램이 여러 스레드를 가질 수 있으며, 각 스레드는 독립적으로 실행된다. 이를 통해 멀티스레딩을 구현하여 동시에 여러 작업을 수행할 수 있다.

🔶 프로세스와 스레드

  • 프로세스: 운영체제에서 실행 중인 프로그램의 인스턴스. 각 프로세스는 독립된 메모리 공간을 가진다.
  • 스레드: 프로세스 내에서 실행되는 작은 작업 단위로, 여러 스레드가 하나의 프로세스 내에서 자원을 공유하며 동시에 실행될 수 있다.

🔶 스레드의 특징

  • 스레드는 독립적으로 실행되지만, 동일한 프로세스 내의 다른 스레드들과 메모리 공간을 공유한다.
  • 각 스레드는 고유의 실행 스택과 프로그램 카운터를 가진다.
  • 멀티스레딩을 통해 응답성을 높이고, 자원의 효율적인 사용이 가능하다.

🔶 스레드 생성 및 사용 방법

Java에서 스레드를 생성하는 방법은 크게 두 가지가 있다.

  1. Thread 클래스 상속
  2. Runnable 인터페이스 구현

🔸🔸 Thread 클래스 상속

class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " - " + i);
        }
    }

    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        thread1.start(); // 스레드 시작
        thread2.start(); // 스레드 시작
    }
}

🔸🔸Runnable 인터페이스 구현
이 방법이 더 선호된다.

class MyRunnable implements Runnable {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " - " + i);
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable());
        thread1.start(); // 스레드 시작
        thread2.start(); // 스레드 시작
    }
}

🤷‍♀️ 멀티스레드 환경

멀티스레드 환경은 여러 스레드가 동시에 실행되는 환경을 의미. Java에서 멀티스레딩을 사용하면, 프로그램이 동시에 여러 작업을 수행할 수 있어 성능을 개선할 수 있으나, 스레드 간의 자원 공유 및 동기화 문제가 발생할 수 있다.

🔶 멀티스레딩의 장점

  • 응답성 개선: 사용자 인터페이스가 멀티스레딩을 사용하면, 긴 작업이 백그라운드에서 실행되는 동안에도 인터페이스가 응답성을 유지할 수 있습니다.
  • 자원 공유: 여러 스레드가 동일한 메모리 공간을 공유하여 자원을 효율적으로 사용할 수 있습니다.
  • 멀티코어 활용: 멀티코어 프로세서를 가진 시스템에서 여러 스레드를 병렬로 실행하여 성능을 향상시킬 수 있습니다.

🔶 멀티스레딩의 단점

  • 복잡성 증가: 스레드 간의 상호작용과 동기화 문제로 인해 프로그램의 복잡성이 증가할 수 있습니다.
  • 데드락: 두 개 이상의 스레드가 서로의 자원을 기다리며 영원히 실행되지 않는 상태에 빠질 수 있습니다.
  • 경쟁 조건: 여러 스레드가 동시에 자원에 접근할 때 발생하는 문제로, 데이터의 일관성이 깨질 수 있습니다.

🤷‍♀️ 동기화와 멀티스레드 환경의 연관성

멀티스레드 환경에서 여러 스레드가 동시에 공유 자원에 접근할 때 데이터 일관성이 깨질 수 있다. 이를 방지하기 위해 동기화(synchronization)가 필요하고, 동기화는 특정 코드 블록이나 메서드에 대해 한 번에 하나의 스레드만 접근할 수 있도록 제한한다. Java에서는 'synchronized' 키워드를 사용하여 동기화할 수 있다.

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

public class SynchronizedExample {
    public static void main(String[] args) {
        Counter counter = new Counter();
        
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();

        try {
            thread1.join(); // 스레드가 종료될 때까지 대기
            thread2.join(); // 스레드가 종료될 때까지 대기
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final count: " + counter.getCount()); // 예상 출력: 2000
    }
}

코드 설명 : increment 메서드와 getCount 메서드는 synchronized 키워드로 동기화되어, 여러 스레드가 동시에 접근할 때 데이터 일관성을 유지할 수 있다.


🤷‍♀️ 개행 문자

개행 문자(newline character)는 줄 바꿈을 나타내는 문자. 운영체제마다 개행 문자가 다르다.

  • Windows: \r\n
  • Unix/Linux/MacOS: \n

Java에서는 System.lineSeparator() 메서드를 사용하여 시스템 독립적인 개행 문자를 사용할 수 있다.


🤷‍♀️ try-with-resources 구문

Java 7에서 도입된 try-with-resources 구문은 자원을 자동으로 닫아주는 기능을 제공한다. AutoCloseable 인터페이스를 구현한 객체는 try 블록이 끝나면 자동으로 닫힌다.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TryWithResourcesExample {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

🤷‍♀️ 버퍼와 버퍼 크기

버퍼는 데이터를 임시로 저장하는 메모리 공간으로 버퍼링을 통해 I/O 작업의 효율성을 높일 수 있다. 버퍼 크기는 버퍼의 크기를 설정하는 것을 의미하며, 적절한 크기의 버퍼를 사용하면 I/O 성능을 최적화할 수 있다.

// 버퍼 크기 설정 예시 코드

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderExample {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("example.txt"), 8192)) { // 8KB 버퍼 크기 설정
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

🤷‍♀️ 데이터를 버퍼에서 파일로 밀어내는 이유

버퍼를 사용하면 여러 번의 작은 I/O 작업을 하나의 큰 작업으로 합쳐서 처리할 수 있다. BufferedWriter에서 flush() 메서드를 호출하면 버퍼에 저장된 데이터를 강제로 파일로 밀어내는데, 이는 버퍼가 가득 차기 전에 데이터를 출력해야 할 때 유용하다.


// flush 예시 코드
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedWriterExample {
    public static void main(String[] args) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
            for (int i = 0; i < 100; i++) {
                bw.write("This is line number " + (i + 1));
                bw.newLine();
                if (i % 10 == 0) {
                    bw.flush(); // 10줄마다 flush() 호출
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

🤷‍♀️ 불변 객체

불변 객체(immutable object)는 생성된 이후 그 상태를 변경할 수 없는 객체. String 클래스가 대표적인 불변 객체이다. 불변 객체는 안전하게 공유될 수 있으며, 멀티스레드 환경에서 데이터 일관성을 유지하는 데 유리하다.


// 불변 객체 예시 코드
public class ImmutableExample {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = s1.concat(", World!");
        System.out.println(s1); // 출력: Hello
        System.out.println(s2); // 출력: Hello, World!
    }
}

🤷‍♀️ 가변 객체

가변 객체(mutable object)는 생성된 이후에도 그 상태를 변경할 수 있는 객체. StringBuilder와 StringBuffer가 대표적인 가변 객체이다. 문자열을 빈번하게 수정할 때 가변 객체를 사용하면 성능이 개선된다.


// 가변 객체 예시 코드
public class MutableExample {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("Hello");
        sb.append(", World!");
        System.out.println(sb.toString()); // 출력: Hello, World!
    }
}

🤷‍♀️ 파일 I/O

파일 I/O는 파일을 읽거나 쓰는 작업을 의미한다. Java에서는 다양한 클래스를 통해 파일 I/O를 수행할 수 있다. FileReader, FileWriter, BufferedReader, BufferedWriter 등이 대표적입니다.


// 파일 읽기 와 쓰기 얘시 코드
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class FileIOExample {
    public static void main(String[] args) {
        // 파일 쓰기
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
            bw.write("Hello, world!");
            bw.newLine();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 파일 읽기
        try (BufferedReader br = new BufferedReader(new FileReader("output.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

🤷‍♀️ 요약

  • 멀티스레드 환경: 여러 스레드가 동시에 실행되는 환경
  • 동기화: 멀티스레드 환경에서 데이터 일관성을 유지하기 위해 사용
  • 개행 문자: 줄 바꿈을 나타내는 문자
  • try-with-resources 구문: 자원을 자동으로 닫아주는 구문
  • 버퍼: 데이터를 임시로 저장하는 메모리 공간
  • 버퍼 크기: 버퍼의 크기 설정, 적절한 크기로 성능 최적화
  • 데이터를 버퍼에서 파일로 밀어내는 이유: I/O 작업의 효율성 향상
  • 불변 객체: 상태를 변경할 수 없는 객체
  • 가변 객체: 상태를 변경할 수 있는 객체
  • 파일 I/O: 파일을 읽거나 쓰는 작업

profile
룰루

0개의 댓글