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가 작동하지 못하기 때문입니다.
}
}
}
FileInputStream
FileOutputStream
read()
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("메인 쓰레드가 종료됩니다. ");
}
}
/* 실행 결과
데몬 쓰레드가 실행중입니다.
데몬 쓰레드가 실행중입니다.
데몬 쓰레드가 실행중입니다.
데몬 쓰레드가 실행중입니다.
메인 쓰레드가 종료됩니다.
*/