


package javaIO;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class JavaIO1 {
public static void main(String[] args) {
FileInputStream fis = null; // finally에서 fis, fos를 닫기위해 현재 scope 에서 선언
FileOutputStream fos = null;
try {
fis = new FileInputStream("src/javaIO/text1.txt"); //FileNotFoundException 처리 필요
fos = new FileOutputStream("src/javaIo/text2.txt"); //FileNotFoundException 처리 필요
int readData = 0;
while((readData = fis.read())!=-1){ //IOException 처리 필요
fos.write(readData); //IOException 처리 필요
}
}catch (Exception e) {
throw new RuntimeException(e);
}finally {
try {
fis.close(); //IOException 처리 필요
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
fos.close(); //IOException 처리 필요
} catch (IOException e) {
throw new RuntimeException(e);
}
//fis.close()와 fos.closse()를 따로 예외처리 하는 이유는
//try 문에 두 개의 close가 있을 때, 더 위에있는 close에서 예외가 발생되면
//그 밑에 있는 close가 작동하지 못하기 때문입니다.
}
}
}FileInputStreamFileOutputStreamread()write()close()// 기존
FileInputStream fis = null; // finally에서 fis, fos를 닫기위해 현재 scope 에서 선언
FileOutputStream fos = null;
try{
fis = new FileInputStream("src/javaIO/text1.txt");
fos = new FileOutputStream("src/javaIo/text2.txt");
} catch(Exception e){
// ...
} final{
try{
fis.close();
}catch(IOException e){
//...
}
try{
fos.close();
}catch(IOException e){
//...
}
}// try-with-resources 적용
try(
FileInputStream fis = new FileInputStream("src/javaIO/text1.txt");
FileOutputStream fos = new FileOutputStream("src/javaIo/text2.txt");
){
//...
} catch(Exception e){
// ...
} ```java
package javaIO;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class JavaIo512 {
public static void main(String[] args) {
long startTime = System.currentTimeMillis(); // 현재 시간을 밀리초로 저장할 수 있다.
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("src/javaIO/text1.txt");
fos = new FileOutputStream("src/javaIo/text2.txt");
int readCount = 0;
byte[] buffer = new byte[512]; //한번에 입력받을 File의 데이터를 저장할 버퍼
while((readCount = fis.read(buffer))!=-1){ //매개변수로 받은 버퍼에 File 데이터를 읽어옵니다.
fos.write(buffer, 0, readCount); //데이터가 담긴 버퍼의 값을 0 부터 읽어온 byte 크기만큼 File에 출력합니다.
}
}catch (Exception e) {
throw new RuntimeException(e);
}finally {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
fos.close(); //IOException 처리 필요
} catch (IOException e) {
throw new RuntimeException(e);
}
}
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
}
}
/* 애국가 전체 가사 복붙 실험
1 byte 씩 진행 : 6 밀리초 소요
512 byte 씩 진행 : 1 밀리초 이하 소요
*/
```
- 추가로 1byte로 읽어들어올 때와 512byte로 읽어들어올 때의 소요시간을 비교해보면
버퍼가 클수록 빨리 읽고 쓰는것을 확인할 수 있다.예시 코드
package javaIO;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class JavaIOVarious {
public static void main(String[] args) {
try ( //try-with-resources 적용
DataOutputStream dos = new DataOutputStream(new FileOutputStream("src/javaIO/variousText.txt"));
DataInputStream dis = new DataInputStream(new FileInputStream("src/javaIO/variousText.txt"));
//다양한 타입을 입출력하기 위한 DataInputStream / DataOutputStream 객체 -> 데코레이터 패턴
) {
// DataOutputStream 메서드
dos.writeInt(100);
dos.writeBoolean(false);
dos.writeDouble(10.25);
// DataIntputStream 메서드
int disInt = dis.readInt();
boolean disBoo = dis.readBoolean();
double disDou = dis.readDouble();
System.out.println(disInt);
System.out.println(disBoo);
System.out.println(disDou);
} catch (Exception e) {
throw new RuntimeException();
}
}
}
/* 실행 결과
100
false
10.25
*/

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class CharIOExam02 {
public static void main(String[] args) {
BufferedReader keyBr = new BufferedReader(new InputStreamReader(System.in));
//키보드로 입력받은 문자열을 저장하기 위해 line변수를 선언
String line = null; //try-catch 밖에서도 사용하기 위해 현재 scope에서 선언
try {
line = keyBr.readLine()
} catch (IOException e) {
e.printStackTrace();
}
//콘솔에 출력
System.out.println(line);
//---------------------------------------------------------------
BufferedReader br = null;
PrintWriter pw = null;
try{
br = new BufferedReader(new FileReader("src/javaIO/exam/CharIOExam02.java"));
pw = new PrintWriter(new FileWriter("test.txt"));
String line = null;
while((line = br.readLine())!= null){
pw.println(line);
}
}catch(Exception e){
e.printStackTrace();
}finally {
pw.close();
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}BufferedReader keyBr = new BufferedReader(new InputStreamReader(System.in));readLine()null을 반환한다.br.close()extends Thread
implements Runnable
다음 챕터에서 확인해보도록 하자
Thread 클래스를 상속 받는다.Thread의 run() 메서드를 오버라이딩 합니다.run() : Thread 실행 시, 작동할 코드를 가지고 있는 메서드package thread1;
public class MyThread extends Thread{
String str;
public MyThread(String str){
this.str = str;
}
@Override
public void run(){ // Thread 실행 시, 작동하는 메서드
for(int i = 0; i<10; i++){
try {
System.out.println(str);
Thread.sleep((int)(Math.random() * 1000)); // 연산속도가 너무 빠르기에 sleep으로 지연을 건다.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}package thread1;
public class ThreadMain {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("*");
MyThread myThread2 = new MyThread("-");
myThread1.start(); // Thread 준비 및 호출자의 run()을 실행
myThread2.start();
System.out.println("main 메서드 끝!!");
}
}
/* 실행 결과
*
main 메서드 끝!!
-
-
*
-
*
-
-
*
*
*/
start()로 시작해야 한다.start()를 호출하면run() 을 실행한다.main() 메서드가 제일 먼저 실행되었다.start() 호출만 하고 바로 끝났지만myThread1과 myThread2은 sleep()을 걸어두었기에 반복문이 끝날때까지 시간이 걸린다.*과 -가 섞여서 출력되는 것을 통해 myThread1과 myThread2가 동시에 실행되고myThread1과 myThread2의 반복문이 모두 끝나면, 그제서야 프로그램이 종료된다.Runnable 인터페이스로 구현한다.Runnable의 run() 메서드를 오버라이딩 합니다.run() : Thread 실행 시, 작동할 코드를 가지고 있는 메서드package thread1;
public class RunnableThread implements Runnable{
String str = null;
public RunnableThread(String str){
this.str = str;
}
@Override
public void run(){
for(int i = 0; i<5; i++){
System.out.println(str);
try {
Thread.sleep((int)(Math.random()*500));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}package thread1;
public class RunnableThreadMain {
public static void main(String[] args) {
RunnableThread runnableThread1 = new RunnableThread("*");
RunnableThread runnableThread2 = new RunnableThread("-");
Thread thread1 = new Thread(runnableThread1);
Thread thread2 = new Thread(runnableThread2);
thread1.start();
thread2.start();
System.out.println("main 메서드는 종료!");
}
}
/* 실행 결과
main 메서드는 종료!
-
*
*
*
-
*
-
*
-
-
*/Runnable로 구현한 클래스의 경우, Thread 클래스를 상속 받은게 아니기 때문에start()메서드를 사용할 수 없다.start()로 Thread를 준비하고 실행시키면 된다.public class MusicBox {
//신나는 음악!!! 이란 메시지가 1초이하로 쉬면서 5번 반복출력
public void playMusicA(){
for(int i = 0; i < 5; i ++){
System.out.println("신나는 음악!!!");
try {
Thread.sleep((int)(Math.random() * 500));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//슬픈 음악!!!이란 메시지가 1초이하로 쉬면서 5번 반복출력
public void playMusicB(){
for(int i = 0; i < 5; i ++){
System.out.println("슬픈 음악!!!");
try {
Thread.sleep((int)(Math.random() * 500));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}public class MusicPlayer extends Thread{
int type;
MusicBox musicBox;
// 생성자로 부터 musicBox와 정수를 하나 받아들여서 필드를 초기화
public MusicPlayer(int type, MusicBox musicBox){
this.type = type;
this.musicBox = musicBox;
}
// type이 무엇이냐에 따라서 musicBox가 가지고 있는 메소드가 다르게 호출
public void run(){
switch(type){
case 1 :
musicBox.playMusicA();
break;
case 2 :
musicBox.playMusicB();
break;
}
}
public class MusicBoxMain {
public static void main(String[] args) {
// MusicBox 인스턴스
MusicBox box = new MusicBox();
MusicPlayer kim = new MusicPlayer(1, box);
MusicPlayer lee = new MusicPlayer(2, box);
// MusicPlayer쓰레드를 실행합니다.
kim.start();
lee.start();
}
}
/* 실행 결과
신나는 음악!!!
슬픈 음악!!!
슬픈 음악!!!
신나는 음악!!!
슬픈 음악!!!
슬픈 음악!!!
신나는 음악!!!
신나는 음악!!!
신나는 음악!!!
슬픈 음악!!!
*/// 공유 객체
package syncthread;
public class Cal {
private int num=5;
public synchronized int plusNum1(){ // 객체 사용권(Monitoring Lock) 영향을 받는 메서드
for(int i = 0; i < 5; i++){
num += 1;
System.out.println("plus 1 : " + num);
try {
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return num;
}
public int plusNum2(){
// i. 코드
int pn2Num = 0; //plusNum1의 작업과는 관계없는 작업은 먼저 실행
for(int j = 0; j < 10000; j++){
pn2Num ++;
}
// ii. 코드
try {
Thread.sleep(1000);
System.out.println("plusNum2 선작업 완료!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// iii. 코드
synchronized (this) { //plusNum1의 결과값을 활용해야하는 부분은 synchronized로 감싸서 Monitoring Lock 해제 대기하기
num = this.num + pn2Num;
for (int i = 0; i < 5; i++) {
num += 2;
System.out.println("plus 2 : " + num);
try {
Thread.sleep((int)(Math.random() * 500));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
return num;
}
}package syncthread;
public class CalPlayer extends Thread{
private Cal cal;
private int type;
public CalPlayer(int type, Cal cal) {
this.type = type;
this.cal = cal;
}
@Override
public void run(){
switch(type){
case 1 :
cal.plusNum1();
break;
case 2 :
cal.plusNum2();
break;
}
}
}package syncthread;
public class CalMain {
public static void main(String[] args) {
Cal cal = new Cal();
CalPlayer cp1 = new CalPlayer(1, cal);
CalPlayer cp2 = new CalPlayer(2, cal);
cp1.start();
cp2.start();
}
}
/* 실행 결과
plus 1 : 6
plus 1 : 7
plus 1 : 8
plusNum2 선작업 완료!
plus 1 : 9
plus 1 : 10
plus 2 : 10012
plus 2 : 10014
plus 2 : 10016
plus 2 : 10018
plus 2 : 10020
*/synchronized 키워드를 사용해야 한다.synchronized 키워드를 사용해야 한다.synchronized 키워드로 감싸줄 수 있다.cp1.start()가 먼저 실행되기에 cp1가 Monitoring Lock을 가진다.cp2.start()도 실행된다.cp1이 먼저 실행 되었기 때문에, 공유 객체에서 cp1이 사용하는 메서드인 plusNum1 메서드가 끝날때까지 cp2가 공유 객체에서 사용하는 코드 중에 객체 사용권의 영향을 받는 코드는 대기해야 한다.pn2Num을 10000번 더하는 for문sleep으로 1초 지연 후, "plusNum2 선작업 완료!" 출력하는 try-catch 문sleep으로 랜덤하게 지연되며 2를 5번 더하는 for문synchronized로 감싸져있기에 Monitoring Lock의 해제를 기다려야한다.plusNum1 메서드가 끝나고나서 공유객체 cal의 num 필드는 10이 된다.plusNum2 메서드에서 pn2Num은 Monitoring Lock의 영향을 받지 않아 cp1 Thread와 동시에 실행되어 10000이 되있는 상태고, cal의 num 필드는 10인 상태로 cp2 코드가 진행된다.plusNum2 메서드의 나머지 코드에서는 10010에 2씩 5번 더한다.num 필드는 10020이 된다.
sleep()wait() | notify()run()yield()join()join()을 호출하게 되면 해당 쓰레드의 run()이 종료될 때까지 대기합니다.run()이 끝날때까지 현재 메서드를 대기시킨다.public class MyThread5 extends Thread{
@Override
public void run(){
for(int i = 0; i < 5; i++){
System.out.println("MyThread5 : "+ i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}public class JoinExam {
public static void main(String[] args) {
MyThread5 thread5 = new MyThread5();
// Thread 시작
thread5.start();
System.out.println("thread5가 종료될때까지 기다립니다.");
try {
// join()을 만나기 전까지는 thread5는 계속 실행되고 있다.
thread5.join(); // 쓰레드 thread5의 run()이 끝날때 까지 현재 Thread 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread5가 종료되었습니다.");
}
}
/* 실행 결과
thread5가 종료될때까지 기다립니다.
MyThread5 : 0
MyThread5 : 1
MyThread5 : 2
MyThread5 : 3
MyThread5 : 4
thread5가 종료되었습니다.
*/wait()과 notify()는 동기화 관계에서만 사용 가능합니다.notify()로 끝났음을 알립니다.package syncthread.waitnotify;
public class ThreadA extends Thread{
@Override
public void run(){
synchronized (this){
for(int i = 5; i > 0; i--){
System.out.println("ThreadA : 카운트다운 : " + i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
notify(); // ThreadB에게 Monitoring Lock을 넘긴다
}
}
}package syncthread.waitnotify;
import java.util.TreeMap;
public class ThreadB extends Thread{
ThreadA a; //synchronized에 인자로 넣어주기 위해 wait()을 호출하는 Thread를 필드로 가지고 있어야 한다.
public ThreadB(ThreadA a){
this.a = a;
}
@Override
public void run(){
synchronized (a){ // wait을 호출하는 Thread의 변수를 인자값으로 넣어야 한다.
try {
System.out.println("ThreadB : 현재 ThreadB가 실행중입니다.");
Thread.sleep(500);
System.out.println("ThreadB : 아 잠시 장비 점검을 하겠다고 하네요");
Thread.sleep(500);
a.wait(); //Monitoring Lock이 ThreadB에서 ThreadA로 넘어가며 ThreadA 실행.
// ThreadB는 notify()를 기다린다.
System.out.println("ThreadB : 점검이 끝났다고 하네요.");
Thread.sleep(500);
System.out.println("ThreadB : 다시 이어가보겠습니다.");
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
package syncthread.waitnotify;
public class WaitNotifyMain {
public static void main(String[] args) {
ThreadA a = new ThreadA();
ThreadB b = new ThreadB(a);
b.start(); //ThreadB가 먼저 실행되었기에 객체사용권(Monitoring Lock)을 가진다
a.start(); //ThreadB가 객체사용권을 가지고 있기에 작동하지 않는다.
}
}
/* 실행 결과
ThreadB : 현재 ThreadB가 실행중입니다.
ThreadB : 아 잠시 장비 점검을 하겠다고 하네요
ThreadA : 카운트다운 : 5
ThreadA : 카운트다운 : 4
ThreadA : 카운트다운 : 3
ThreadA : 카운트다운 : 2
ThreadA : 카운트다운 : 1
ThreadB : 점검이 끝났다고 하네요.
ThreadB : 다시 이어가보겠습니다.
*/🔥
wait()과join()의 차이
wait()|noftify()
- 공유 자원에 대한 접근을 제어하고, 특정 조건이 충족될 때까지 대기하는 데 사용됩니다.
- 반드시 synchronized 블록 내에서 사용해야 합니다.
- 동기화되어 있기에 객체사용권(Monitoring Lock) 영향을 받으며 작동한다.
join()
- 특정 스레드가 종료될 때까지 대기하는 데 사용됩니다.
- 동기화 되어 있지 않기에, join()을 호출하기 전까지는 join()을 호출하는 Thread나 현재 Thread나 모두 동시에 실행된다.
start()로 실행하기 전에 .setDaemon(true) 메서드로 설정 가능하다 // Runnable을 구현하는 DaemonThread클래스를 작성해 보았다.
public class DaemonThread implements Runnable {
// 무한루프안에서 0.3초씩 쉬면서 데몬쓰레드가 실행중입니다를 출력하도록 run()메소드를 작성
@Override
public void run() {
while (true) {
System.out.println("데몬 쓰레드가 실행중입니다.");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
break; //Exception발생시 while 문 빠찌도록
}
}
}
public static void main(String[] args) {
// Runnable을 구현하는 DaemonThread를 실행하기 위하여 Thread 생성
Thread th = new Thread(new DaemonThread());
// 데몬쓰레드로 설정
th.setDaemon(true);
// 쓰레드를 실행
th.start();
// 메인 쓰레드가 1초뒤에 종료되도록 설정.
// 데몬쓰레드는 다른 쓰레드가 모두 종료되면 자동종료.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("메인 쓰레드가 종료됩니다. ");
}
}
/* 실행 결과
데몬 쓰레드가 실행중입니다.
데몬 쓰레드가 실행중입니다.
데몬 쓰레드가 실행중입니다.
데몬 쓰레드가 실행중입니다.
메인 쓰레드가 종료됩니다.
*/