❯ InputStream, OutputStream
자바에서는 입출력을 다루기 위한 InputStream, OutputStream을 제공함
스트림은 단방향으로만 데이터를 전송할 수 있으므로 입력, 출력을 위한 각각의 스트림이 필요함
FileInputStream
프로젝트(코드)와 파일(.txt)이 같은 디렉토리에 있을때,
FileInputStream을 통해 해당 txt파일의 내용을 코드 내부로 가져올 수 있다.
BufferedInputStream이라는 보조 스트림을 사용하면 성능이 향상된다. (아래 코드에서 (1)에 해당)
Buffer란 바이트 배열로 여러 바이트를 저장해서 한번에 많은 데이터를 입출력할 수 있도록 돕는 임시 저장공간이다.
public static void main(String args[])
{
try {
FileInputStream fileInput = new FileInputStream("파일이름.txt");
BufferedInputStream bufferedInput = new BufferedInputStream(fileInput); // (1)
int i = 0;
while ((i = bufferedInput.read()) != -1) { //fileInput.read()의 리턴값을 i에 저장한 후, 값이 -1인지 확인합니다.
System.out.print((char)i);
}
fileInput.close();
}
catch (Exception e) {
System.out.println(e);
}
}
FileOutputStream
input과 마찬가지로, 아래 코드를 실행하면 같은 디렉토리 내에 '내용'을 포함하고 '파일이름'을 가진 파일이 생성된다.
public static void main(String args[]) {
try {
FileOutputStream fileOutput = new FileOutputStream("파일이름.txt");
String word = "내용";
byte b[] = word.getBytes();
fileOutput.write(b);
fileOutput.close();
}
catch (Exception e) {
System.out.println(e);
}
}
❯ FileReader / FileWriter
위의 입출력 스트림은 바이트(1byte) 기반 스트림이다.
java에서 char(문자) 타입은 2byte인데, 이를 위해 문자 기반 스트림으로 FileReader와 FileWriter가 제공된다.
FileReader는 인코딩을 유니코드로, FileWriter는 유니코드를 인코딩으로 변경한다.
FileReader
FileInputStream과 마찬가지로, BufferedReader가 존재한다. (아래 코드의 (1) 부분)
public static void main(String args[]) {
try {
String fileName = "파일이름.txt";
FileReader file = new FileReader(fileName);
BufferedReader buffered = new BufferedReader(file); // (1)
int data = 0;
while((data=buffered.read()) != -1) {
System.out.print((char)data);
}
file.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
FileWriter
public static void main(String args[]) {
try {
String fileName = "파일이름.txt";
FileWriter writer = new FileWriter(fileName);
String str = "내용";
writer.write(str);
writer.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
❯ File 클래스
자바에서는 File 클래스를 통해 파일과 디렉토리에 접근할 수 있다.
public static void main(String args[]) throws IOException {
File file = new File("../testfile.txt");
System.out.println(file.getPath());
System.out.println(file.getParent());
System.out.println(file.getCanonicalPath());
System.out.println(file.canWrite());
}
❯ 스레드(Thread)
- 프로세스(Process) : 실행중인 애플리케이션. 애플리케이션을 실행하면 운영체제로부터 필요한만큼 메모리 할당받아 프로세스가 된다. 데이터, 컴퓨터 자원, 스레드로 구성됨
- 스레드(Thread) : 스레드는 데이터와 애플리케이션이 확보한 자료를 활용해 소스코드를 실행시킨다. 하나의 코드의 실행 흐름을 의미한다. 스레드에는 메인 스레드, 멀티 스레드가 있음
ex) 메인 스레드가 메인 메서드를 실행시킨다. 만약 소스코드가 싱글 스레드라면 해당 애플리케이션이 프로세스가 될 때 오직 메인 스레드만 가지는 싱글 스레드 프로세스가 된다.
ex) 하나의 프로세스가 여러개의 스레드를 가지면 멀티 스레드 프로세스라고하며, 여러 스레드가 동시 작업이 가능해진다. 메신저 프로그램에서 메세지를 보내면서 동시에 사진을 다운받는것이 이에 해당한다.
❯ 스레드의 생성과 실행
메인 스레드 외에 별도의 작업 스레드를 생성한다는 것은, 작업 스레드가 수행할 코드를 작성하고 작업 스레드를 생성하여 실행시키는 것.
단, 자바는 객체지향 언어이므로 스레드가 수행할 코드도 클래스 내부에 작성해야하며 run() 메서드 내에 스레드 작업 내용이 규정되어야함
run( )메서드는 Runnable 인터페이스와 Thread 클래스에 정의되어있음. 따라서 두가지 생성/실행 방법으로 구분된다.
- Runnable 인터페이스를 구현한 객체에서 run( ) 을 구현하여 스레드를 생성하고 실행
- Thread 클래스를 상속받은 하위 클래스에서 run( ) 을 구현하여 스레드를 생성하고 실행
=> 작업 스레드를 통해 코드를 수행하면, 작업 스레드와 메인 스레드가 동시에 병렬로 실행되는것을 알 수 있음.
1) Runnable 인터페이스를 구현한 객체에서 스레드를 생성하고 실행하는 방법
public class ThreadExample1 {
public static void main(String[] args) {
// Runnable 인터페이스 구현한 객체 생성
Runnable task1 = new ThreadTask1();
// Runnable 구현한 객체를 인자로 전달하며 Thread 클래스를 인스턴스화 해서 스레드를 생성함
Thread thread1 = new Thread(task1);
// 작업 스레드를 실행시킴
thread1.start();
// 메인 스레드 외에 스레드가 생성되었는지 확인하기 위해 반복문 추가
for (int i = 0; i < 100; i++) {
System.out.print("@");
}
}
}
// Runnable 인터페이스를 구현한 내부클래스 정의
class ThreadTask1 implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.print("#");
}
}
}
위 코드를 실행시키면 아래와 같이 나타나며, 메인스레드에서 출력된 @ 와 추가 생성된 스레드에서 출력된 # 가 모두 확인되고 있다.
2) Thread 클래스를 상속받은 하위 클래스에서 스레드를 생성하고 실행하는 방법
public class ThreadExample2 {
public static void main(String[] args) {
ThreadTask2 thread2 = new ThreadTask2();
// 작업 스레드를 실행시켜, run() 내부의 코드를 실행한다.
// Runnable 인터페이스때와 달리 Thread 클래스를 직접 인스턴스화 하지는 않는다.
thread2.start();
// 메인스레드와 추가 생성된 스레드 구분을 위해 반복문 추가
for (int i = 0; i < 100; i++) {
System.out.print("@");
}
}
}
// Thread 클래스를 상속받는 클래스 정의
class ThreadTask2 extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.print("#");
}
}
}
코드를 실행하면 1) 때와 마찬가지로 두개의 스레드가 동시에 동작하고있다.
❯ 스레드의 이름
메인 스레드는 main 이라는 이름을 가지며, 그 외에는 기본적으로 Thread - n 의 형태를 가짐.
스레드참조값.getName() // 스레드 이름 조회
스레드참조값.setName(변경할 이름) // 스레드 이름 설정
스레드참조값.currentThread().getName() // currentThread()는 실행중인 스레드의 주소값을 나타냄.
// getName()붙여서 실행중인 스레드 이름 확인 가능
❯ 스레드의 동기화
멀티 스레드의 경우, 여러 스레드가 동일한 데이터를 공유하게되면서 문제가 발생할 수 있음(상호간 영향 받을 수 있음)
이를 방지하기 위해 동기화를 적용하는데 이 때 필요한 개념이 '임계영역'과 '락'임
- 임계영역 (Critical section) : 오로지 하나의 스레드만 코드를 실행할 수 있는 코드영역. synchronized 키워드 활용.
특정 메서드 전체를 임계영역으로 설정하거나, 메서드 내의 특정 영역을 임계 영역으로 설정할 수 있음 - 락(Lock) : 임계영역을 포함한 객체에 접근할 수 있는 권한
임계영역으로 설정된 객체가 다른 스레드에 의해 작업이 이루어지고 있지 않을 때, 임의의 스레드 A는 해당 객체에 대한 락을 획득하여 임계영역 내의 코드를 실행할 수 있다.
1) 메서드 전체를 임계영역으로 지정하기
메서드 반환타입 좌측에 synchronized 키워드를 작성하면 메서드 전체가 임계영역이 된다.
public synchronized boolean withdraw(int money) {
if (balance >= money) {
try { Thread.sleep(1000); } catch (Exception error) {}
balance -= money;
return true;
}
return false;
}
2) 특정한 영역을 임계영역으로 지정하기
synchronized 키워드와 함께 소괄호(해당 영역이 포함된 객체 참조 넣기), 중괄호(해당되는 코드)를 사용해 임계영역을 설정한다.
public boolean withdraw(int money) {
synchronized (this) {
if (balance >= money) {
try { Thread.sleep(1000); } catch (Exception error) {}
balance -= money;
return true;
}
return false;
}
이제 정확하게 4주간의 과정이 지나갔다. 다음주부터는 또 새로운 과정을 시작하게된다.
확실히 부트캠프를 통해 학습을 하니 공부하는 습관도 생기고 커리큘럼을 믿고 따라갈 수 있는것같다.
이번 주말동안 부족했던 부분을 확실히 보충하고! 다음주부터는 또 새로운 마음가짐으로 임해야겠다 :D
'부트캠프 개발일기 > Java' 카테고리의 다른 글
18일차: 스트림(Stream) (0) | 2023.03.09 |
---|---|
17일차: Java 심화(Annotation, Lambda) (0) | 2023.03.08 |
16일차: 컬렉션(Collection Framework) (0) | 2023.03.07 |
15일차: 컬렉션(Enum, Generic, Exception Handling) (0) | 2023.03.06 |
14일차: 미니 프로젝트(의존성 주입) (0) | 2023.03.03 |