[Java] Stream, IO, Threaad

JH·2023년 3월 30일

Java

목록 보기
7/21

1. TIL

A. Input ,Output

1. 메인스트림 (Main Stream)

스트림 : 데이터를 연결하는 통로, 단방향 통신

대상 : 파일, 메모리(Array), 프로세스, 메모리(String) / 주로 파일을 사용
int로 하나씩 읽을 수 있음, 배열을 이용하여 전체를 저장하고 한꺼번에 읽을 수 읽음

바이트 스트림 (Byte Stream) : InputStream, OutputStream

데이터를 바이트(byte)단위로 주고 받음

Ex) Byte Stream

public class Ex01ByteStream {
	// outputStream : fileSave(), 파일 저장
	public static void fileSave() throws IOException {
		FileOutputStream fos = new FileOutputStream("ByteStream.txt");
		fos.write(97); // int 타입
		
		byte[] data = {65, 66, 67}; // byte 타입
		fos.write(data);	
		
		fos.close(); // 자원 반환
	}
	
	//inputStream : fileOpen(), 파일 불러오기
	public static void fileOpen() throws IOException {
		FileInputStream fis = new FileInputStream("ByteStream.txt");
	
		// ver1 int로 하나씩 읽어들이기
//		int data;
//		while ( (data = fis.read()) != -1) { // read : -1이 나올 때 까지 읽음
//			System.out.println((char)data);
//		}
		
		// ver2 byte로 읽어들이기, 한번에 가져와서 읽어 들임
        //length는 long으로 반환 하므로 int로 명시적 형변환
		int fileSize = (int)new File("ByteStream.txt").length(); 
		byte[] readData = new byte[fileSize];
		
		fis.read(readData);
		for(int i = 0; i < readData.length; i++) {
			System.out.println((char)readData[i]);
		}
		fis.close();
	}

	public static void main(String[] args) {
		try {
//			fileSave();
			fileOpen();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

문자 스트림 (Character Stream) : Reader, Writer

입출력 단위가 문자(char, 2 byte)인 스트림

Ex) Character Stream

public class Ex02CharacterStream {
	// FileWriter : fileSave()
	public static void fileSave() throws IOException {
		FileWriter fw = new FileWriter("CharacterStream.txt");
		
		char[] city = {'서', '울'};
		fw.write(city);
		fw.write("뉴욕");
		
		fw.close();
	}
	
	// FileReader : fileOpen()
	// ver1
	public static void fileOpen() throws IOException {
		FileReader fr = new FileReader("CharacterStream.txt");
	
		int data;
		while ( (data = fr.read()) != -1) {
			System.out.println((char)data);
		}
		fr.close();
	}
	
	// ver2
	  public static void fileOpen2() throws IOException {
			// try ~ with ~ resource : AutoClose
			try(FileReader fr = new FileReader("CharacterStream.txt")) {
				
				int data;
				while ( (data = fr.read()) != -1 ) {
					System.out.println((char)data);
				}
			}
		}
	
	public static void main(String[] args) {
		try {
			fileSave();
			fileOpen2();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

close()로 외부 자원을 꼭 반환 해야함

try ~ with ~ resource : try-catch보다 업그레이드 됨, AutoClose를 내포함


표준 입출력 : 콘솔(console, 화면)을 통한 데이터의 입출력

  • 입력 방법 : Scanner, System.in
  • 출력 방법 : System.out (Print Stream임)

2. 보조스트림

스트림의 기능을 향상시키거나 새로운 기능을 추가하기 위해 사용
보조 스트림은 메인스트림이 있어야 사용 가능

바이트기반 보조스트림
버퍼 (Buffered)

버퍼를 이용하여 입출력을 향상 시킴, 기본적으로 8192byte 크기를 갖게됨
메인 스트림이 있어야 사용 가능, 400ms → 10ms 로 40배 정도 성능이 향상됨

Ex) Buffered Stream + 표준 입력

public class Ex03BufferedStream {
	
	public static void usedBufferedStream() throws IOException {
		long start;
		long end;
		
		FileInputStream fis = new FileInputStream("church.webp");
		BufferedInputStream bis = new BufferedInputStream(fis); // 메인 스트림이 있어야 사용 가능
		
		start = System.currentTimeMillis();
		while(bis.read() != -1) {}
		end = System.currentTimeMillis();
		
		System.out.println("버퍼 사용 : " + (end-start) + "ms");
	}

	public static void main(String[] args) {
		try {
			usedBufferedStream();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// 표준 입력 (시스템 입력)
		Scanner inData = new Scanner(System.in);
		int i = inData.nextInt();
		System.out.println(i + 10);
	}
}

3. 직렬화 (serialization)

객체를 연속적인 데이터로 변환하는 것, 외부로 객체를 내보내려면 꼭 해야함
직렬화 방법 : implements Serializable

버전관리 : serialVersionUID 를 지정하여 내부, 외부를 독립적으로 관리

transient을 사용 : 외부에 노출되면 안되는 값에 사용

Ex) Serializtion

public class Ex04Serialization {

	public static void main(String[] args) {
		// 1. 객체 출력 : java -> 파일
		try {
			FileOutputStream fos = new FileOutputStream("Person.txt");
			ObjectOutputStream oos = new ObjectOutputStream(fos);
		
			Person personOut = new Person("java", 29, 1001);
		
			oos.writeObject(personOut);
			
			oos.flush(); // Buffered와 유사한 기능, 데이터를 전부 처리하고 비워줌
			
			oos.close();
			fos.close(); // 처음 만들어진게 마지막에 닫혀야 함
		
			
			// 2. 객체 입력 : 파일 -> java
			FileInputStream fis = new FileInputStream("Person.txt");
			ObjectInputStream ois = new ObjectInputStream(fis);
			
			Person personIn = (Person)ois.readObject();
			System.out.println(personIn);
			
			ois.close();
			fis.close();
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

Ex) Serialization Model

public class Person implements Serializable {
	private static final long serialVersionUID = 4046439826317925371L;
	private String name;
	private int age;
	private transient int personalNumber;

4. Properties

어플리케이션의 환경설정에 관련된 속성을 저장하는데 사용, 키와 값으로 구성

  • DB.Properies 파일은 공유 금지

Ex) Properties

public class Ex05Properties {
	// key를 통해 값을 출력 (HashMap)
	public static void main(String[] args) {
		Properties properties = new Properties();
		
		try {
			properties.load(new FileInputStream("db.properties"));
			System.out.println(properties.getProperty("username"));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

db.properties

username=java
password=java


B. Thread

1. 스레드

프로그램 : 실행 가능한 파일(HDD, SSD)
프로세스 : 실행 중인 프로그램(메모리)

프로세스 : 실행 중인 프로그램, 자원(resources)과 스레드(Thread)로 구성

스레드 : 프로세스 내에서 실제 작업을 수행
모든 프로세스는 최소한 하나의 스레드를 가지고 있음
출력 구조 : [이름, 우선순위, 그룹]

멀티 테스킹(멀티 프로세싱) : 동시에 여러 프로세스를 실행시키는 것
멀티 쓰레딩 : 하나의 프로세스 내에 동시에 여러 스레드를 실행시키는 것

멀티스레드의 장단점
효율적, 응답성(responseness) 향상, 코드가 간결해짐
동기화(synchronization)에 주의해야함, 교착상태(dead-lock) 발생 주의

Ex) Thread 클래스를 상속해서 구현

// 스레드 생성 - Thread 클래스
public class Ex01Thread extends Thread{

	public static void main(String[] args) {
		// 실행
		Ex01Thread thread1 = new Ex01Thread();
		thread1.setName("스레드1");
		thread1.start();
		System.out.println(thread1);
		
		Ex01Thread thread2 = new Ex01Thread();
		thread2.setName("스레드2");
		thread2.start();
		System.out.println(thread2);	
	}
}

Ex) Runnable Interface를 상속해서 구현

// 스레드 생성 - Runnable 인터페이스 구현
public class Ex02Thread implements Runnable{

	@Override
	public void run() {
		for(int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + " - " + (i + 1) + "번 실행");
		}
	}
	
	public static void main(String[] args) {
		Runnable runThread1 = new Ex02Thread();
		Thread thread1 = new Thread(runThread1);
		thread1.setName("스레드1");
		
		Runnable runThread2 = new Ex02Thread();
		Thread thread2 = new Thread(runThread2);
		thread2.setName("스레드2");
		
		// 우선순위 (기본이 5, 1~10)
		thread1.setPriority(Thread.MAX_PRIORITY);
		thread2.setPriority(Thread.MIN_PRIORITY);
		
		thread1.start();
		thread2.start();
	}
}

2. Synchronized

하나의 쓰레드가 접근하면 다른 쓰레드는 접근하지 못함

Ex) Synchronized

class ATM implements Runnable{
	private int deposit = 10000; // 공동 자원
	
	public void withdraw(int money) {
		if(deposit > 0) {
			deposit -= money;
			System.out.println(Thread.currentThread().getName() + " 출금 완료");
			System.out.println("현재 잔액은 : " + deposit + " 입니다.");
		} else {
			System.out.println(Thread.currentThread().getName() + " 잔액 부족");
		}
	}
    
	// ver1 : synchronized (보호 객체) {실행 블럭}
	@Override
	public void run() {
		synchronized (this) {
			for(int i = 0; i < 5; i++) {
				withdraw(1000); // 출금
			}			
		}
	}
    
	// ver2 : 메소드 자체를 synchronized
//	@Override
//	public synchronized void run() {
//			for(int i = 0; i < 5; i++) {
//				withdraw(1000); // 출금
//			}			
//	}
}


public class Ex03Synchronized {
	public static void main(String[] args) {
		ATM atm = new ATM();
		
		Thread John = new Thread(atm, "존");
		John.start();
		
		Thread Kim = new Thread(atm, "김");
		Kim.start();
	}
}

Ex2) Synchronized2

// num이 7이 아닐때에만 setNum 동작하는 기능
class NumberChange {
	int num; // 공유 자원
	
	public synchronized void setNum(int num) {
		if(this.num != 7) { // this num = 0
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			this.num = num; // 파라미터로 들어온 num이 할당됨
			System.out.println(num);
		}else {
			System.out.println("num 값을 변경할 수 없습니다.");
		}
	}
}

public class Ex04Synchronized {
	public static void main(String[] args) {
		NumberChange nc = new NumberChange();
		
		// 스레드 1 
		Thread thread1 = new Thread(new Runnable() {
			@Override
			public void run() {
				nc.setNum(7);
			}
		});

		// 스레드 2 
		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				nc.setNum(10);
			}
		});
		System.out.println(thread1.getState()); // New
		thread1.start();
		thread2.start();
		System.out.println(thread1.getState()); // Runnable
		
		// 스레드1이 먼저 실행 : 7 -> else문의 msg
		// 스레드2가 먼저 실행 : 10 -> 7
	}
}

쓰레드의 상태

  • NEW : 쓰래드가 생성되고 start() (호출)되지 않은 상태

  • RUNNABLE : 실행 중 또는 실행 가능한 상태

  • BLOCKED : 동기화블럭에 의해서 일시정지된 상태

  • WAITING : 작업이 종료되고 일시정지된 상태

  • TERMINATED : 작업이 종료된 상태

쓰레드 실행 메소드

  • sleep() : N(ms) 이후 실행

  • interrupt() : 대기상태(WAITING)인 쓰레드를 실행대기 상태(RUNNABLE)로 만듦

  • yield() : 남은 시간을 다음 쓰레드에게 양보하고, 자신(현재 쓰레드)은 실행 대기함

  • join() : 지정된 시간동안 특정 쓰레드가 작업하는 것을 기다림

쓰레드의 동기화

  • synchronized : 한 번에 하나의 쓰레드만 객체에 접근할 수 있도록 객체에 락(lock)을 걸어서 데이터의 일관성을 유지하는 것

  • wait() : 객체의 lock을 풀고 쓰레드를 해당 객체의 waiting pool에 넣음

  • notify() : waiting pool에서 대기중인 쓰레드 중의 하나를 깨움

  • notifyAll() : waiting pool에서 대기중인 모든 쓰레드를 깨움

쓰레드 종료 권장 방법

  • flag 이용
  • interrupted()

Ex) Thread 메소드 사용

// 스레드를 정지 : flag, stop을 이용
class ThreadA extends Thread{
	private boolean stop = false;
	private boolean flag = true;
	
	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	
	public void setStop(boolean stop) {
		this.stop = stop;
	}
	
	@Override
	public void run() {
		while(!stop) {
			System.out.println(Thread.currentThread().getState());
			if(flag) {
				System.out.println("ThreadA는 작업중");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}else {
				Thread.yield();
			}
		}
	}
}

class ThreadB extends Thread{
	private boolean stop = false;
	private boolean flag = true;
	
	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	
	public void setStop(boolean stop) {
		this.stop = stop;
	}
	
	@Override
	public void run() {
		while(!stop) {
			if(flag) {
				System.out.println("ThreadB는 작업중");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}else {
				Thread.yield();
			}
		}
	}
}

class SumThread extends Thread {
	private long sum;
	
	public long getSum() {
		return sum;
	}
	
	@Override
	public void run() {
		for(int i = 1; i <= 100; i++) {
			sum += i;
		}
	}
}

public class Ex05ThreadControl {
	public static void main(String[] args) {
		for(int i = 1; i < 11; i++) {
			System.out.println(i);
			try {
				Thread.sleep(1000);
				System.out.println(Thread.currentThread().getName());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		
		// yield(), stop()
//		ThreadA threadA = new ThreadA();
//		ThreadB threadB = new ThreadB();
		
		// A, B 시작
//		threadA.start();
//		threadB.start();
		
		// B만 실행하게 하고싶다면?
//		threadA.setFlag(false);
		
		// A를 확실하게 종료하고 싶다면?
//		threadA.setStop(true);
		
		
		
		// join()
		SumThread sumThread = new SumThread();
		sumThread.start();
		
		try {
			// 메인 스레드를 일시정지 -> sumThread가 최종 실행된 후 다시 재실행
			sumThread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("총 합은 : " + sumThread.getSum());		
	}
}

Ex) notify

// 구조 : 하나의 객체에서 두 개의 메소드가 교착로 작동함
class WorkObject {
	public synchronized void methodA() {
		System.out.println("WokerA의 메소드 작업 실행");
		notify(); // 대기중인 WokerB를 깨움
		try {
			wait(); // A는 잠자러 감
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public synchronized void methodB() {
		System.out.println("WokerB의 메소드 작업 실행");
		notify(); // 대기중인 WokerA를 깨움
		try {
			wait(); // B는 잠자러 감
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class WokerA extends Thread{
	private WorkObject workObject;
	
	public WokerA(WorkObject workObject) {
		this.workObject = workObject;
	}
	
	@Override
	public void run() {
		for(int i=0; i<5; i++) {
			workObject.methodA();
		}
	}
}

class WokerB extends Thread{
	private WorkObject workObject;
	
	public WokerB(WorkObject workObject) {
		this.workObject = workObject;
	}
	
	@Override
	public void run() {
		for(int i=0; i<5; i++) {
			workObject.methodB();
		}
	}
}

public class Ex06Thread {
	public static void main(String[] args) {
		WorkObject sharedObject = new WorkObject();
		
		WokerA wokerA = new WokerA(sharedObject);
		WokerB wokerB = new WokerB(sharedObject);
		
		wokerA.start();
		wokerB.start();
	}
}

Ex) interrupt

package step01;

class InterruptedThread extends Thread{
	
	@Override
	public void run() {
		while(true) {
			System.out.println("무한 실행");
			
			if(Thread.interrupted()) {
				System.out.println("종료");
				break;
			}
		}
	}
}

public class Ex07ThreadStop {

	public static void main(String[] args) {
		InterruptedThread iThread = new InterruptedThread();
		
		iThread.start();
		
		try {
			iThread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		iThread.interrupt();
	}
}


C. 데몬 쓰레드 (daemon thread)

일반 쓰레드(non-daemon thread)의 작업을 돕는 보조적인 역할을 수행

일반 쓰레드가 모두 종료되면 자동적으로 종료됨

가비지 컬렉터, 자동저장, 화면자동갱신 등에 사용됨


D. 쓰레드 그룹(ThreadGroup)

서로 관련된 쓰레드를 그룹으로 묶어서 다루기 위한 것
모든 쓰레드는 반드시 하나의 쓰레드 그룹에 포함되어 있어야 함
그룹 포함을 미지정하면 Main이라 그룹으로 지정됨

// 데몬 스레드
class AutoSaveThread extends Thread{
	
	public void save() {
		System.out.println("자동 저장");
	}
	
	@Override
	public void run() {
		while(true) {
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			save();
		}
	}
}

// 스레드 그룹 : 지정하지 않으면 main에 들어감
class Worker extends Thread {
	
	public Worker(ThreadGroup groupName, String threadName) {
		super(groupName, threadName);
	}
	
	@Override
	public void run() {
		while(true) {
			
			System.out.println("무한 업무.");
			
			if(Thread.interrupted()) {
				System.out.println("업무 종료.");
				break;
			}
		}
		System.out.println(getName() + "종료.");
	}
}

public class Ex08DaemonThread {

	public static void main(String[] args) {
		// 데몬 쓰레드
		AutoSaveThread auto = new AutoSaveThread();
		
		auto.setDaemon(true);
		auto.start();
		
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		// 스레드 그룹
//		ThreadGroup dev = new ThreadGroup("dev");
//		
//		Worker java = new Worker(dev, "java");
//		Worker python = new Worker(dev, "python");
//		
//		java.start();
//		python.start();
//
//	
//		dev.interrupt();
	}
}


2. 에러

Ex) Thread 메소드 사용에서 try-catch문을 주석처리하면

다른 분은 0 나오지만 나는 총합이 5050 으로 나온다.

생략된 main Thread, sumThread 중에 무엇을 먼저 시작할 지 명시하지 않아서 그런 것 같다.

같은 코드이지만 운영 체제나 환경에 따라서 출력이 바뀌는 것 같다.


3. 보완 해야 할 것

우리는 Byte Stream 에서 File을 주로 다루니 더 보완하면 좋을 것 같음

보조 스트림은 Buffer 하나 만 다뤘지만 매우 중요한 개념이니 눈에 읽혀야 겠음


4. 느낀점

오늘 배운 Input Output만 해도 본문에 다루지 않았지만 매우 많은 종류가 있었다.

쓰레드에 관한 내용은 컴퓨터를 다루다보니 배경 지식이 있었지만 이것을 활용하고 원하는 로직대로 구현하는 것은 정말 어려운 일이 될 것 같다.

이제 다양한 문법으로 표현할 수 있고 점차 구조나 타입에 익숙해지는 것 같으니 리팩토링이나 백지코딩을 해보면 좋을 것 같음

profile
잘해볼게요

0개의 댓글