개념


I/O (입출력) - 입력 , 출력 / 두 대상 간의 데이터를 주고 받는 것



Stream -  데이터를 운반 하는데 사용되는 연결통로

         -  연속적인 흐름

         -  입출력을 동시에 수행하려면, 2개의 스트림이 필요하다.





간단한 코드지만 아래의 코드를 살펴보자.


import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;


public class FileTest {


public static void main(String args[]) {


FileInputStream fis = null;

FileOutputStream fos = null;


try {

fis = new FileInputStream("log.txt");

fos = new FileOutputStream("back_log.txt");


int data = 0;

byte[] b = new byte[1];


while ((data = fis.read(b)) != -1) {

System.out.println(data);

fos.write(b);

fos.flush();

}


} catch (FileNotFoundException e) {

// TODO Auto-generated catch block

System.out.println("File Not found");

e.printStackTrace();

} catch (IOException e1) {

// TODO Auto-generated catch block

System.out.println("File Not found");

e1.printStackTrace();

}finally {

if(fos!=null) {

try {

fos.close();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

if(fis!=null) {

try {

fis.close();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}


}

}




네트워크, 특히 서버에서 예외 처리와 close() 메소드는 매우 중요하다.


만일 close() 메소드를 try 문에 포함시켜서 한다고 가정하자. 


서버에서 예외가 발생하면 스트림은 닫히지 않고 메모리 누수가 발생할 요소가 있다.


꼼꼼한 예외처리와 try catch, finally 는 자바 네트워크에서 중요한 요소이다.





스트림의 종류


바이트 기반 스트림 


  - inputstream , outputstream


  - 1byte 단위




 문자 기반 스트림

 

  - reader, writer

  -  문자 단위














 개념



Process - 실행중일 프로그램.  쓰레드와 자원으로 구성


Thread -   하나의 작업을 실행하는 작업 단위.




interruptedexception - 서버가 도중에 끊키거나 서버에 문제가 생겼을 때 발생하는 예외



Thread 의 스케쥴링은 자바  jvm 내에서 작업의 순서가 저장된다.


- > 작업의 우선순위를 보장받지 못한다는 말과 같다.





 예제를 보면서 이해해보자..


import Thread.Thread1;

import Thread.Thread2;


public class Test {


public static void main(String[] args) {


Thread1 thread1 = new Thread1();

thread1.start();


Thread thread2 = new Thread(new Thread2());

thread2.start();

Thread thread3 = new Thread();

thread3.start();

int sum = 0;


for (int i = 0; i < 10; i++) {

sum = +i;

try {

Thread.sleep(500);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

System.out.println("Main Thread = " + sum);


}


}

}






코드를 아래와 같은 그림으로 나타 낼 수 있다.








=> 메인 쓰레드에서 쓰레드 3개가 돌고 있다...         쉽다.





또한  setPriority()  메소드를 통해 우선순위를 설정할 수 있다.







멀티쓰레드의 장 단점



 장점

  - 자원을 보다 효율적으로 사용한다.

  - 사용자에 대한 응답성이 향상된다.

  - 작업이 분리되어 코드가 간결해진다.

 단점

  - 동기화에 주의

  - 교착상태가 발생하지 않게 조심 해야한다.

  - 쓰레드가 효율적으로 실행되게끔 고려해야한다.











Thread Groop 


- 관련된 쓰레드를 그룹으로 다루는 것이다.


- 쓰레드 그룹은 반드시 하나 이상 포함된다. 포함하지 않으면 자연스럽게 메인 쓰레드에 포함된다.


- 쓰레드 그룹으로 부터 우선순위를 상속받기 때문에, 같은 쓰레드 그룹에 있는 쓰레드들은 우선순위를 공유한다.. 


(쉽다)










 Daemon Thread



-    어렵게 생각할 것 없다. 데몬은 쓰레드가 계속 실행되고 있는 상태이다.

-    가장 간단하게 while 문으로 무한 루프 돌리면 된다.. 























Synchronized Block, Synchronized method



동기화(Synchronized)


- 공유 데이터(나머지 synchronized 된부분) 에 lock을 걸어 작업중이던 쓰레드가 마칠때까지 다른 쓰레드에게 제어권이 넘어가지 않게 보호한다.

- 교착상태에 주의해야 한다.



쓰레드는 같은 프로세스 내 데이터를 공유하기 때문에 일관성에 영향을 끼칠 수 있다. 

만약 공유하는 자원을 처리하던 중 다른 쓰레드가 접근하기라도 한다면 ? 


즉 동기화를 통해, 위와 같은 상황을 방지하고 어느 한 시점에 하나의 쓰레드만 접근하여 자원을 선점하고 프로그래머는 이를 효율적으로 관리할 필요가 있다. 


예를 들어, 작업 중인 문서에 누군가 들어와 편집을 해서 내가 했던 자료가 날라갔다고 가정하면 큰 낭패 일 것이다. 작업중인 문서에 lock을 걸어 다른 사람의 편집권한을 막는 것과 같은 이치라고 생각하면 된다.


ex) Singleton Pattern의 인스턴스를 여러 객체가 접근 할 때, Static으로 정의된 객체를 여러 객체가 접근 할 때, 하나의 객체에 여러 개의 쓰레드가 접근하려고 할 때.. 




 





사용법


(1) 특정 객체 인스턴스에 lock을 걸려고 할 때 - synchronized block

private Worker wokrer = new Worker();


public void work(){

synchronized(worker){


//input


}


}



(2) 메소드에 lock을 걸 때 - synchronized method

public synchronized work(){


//input code

Worker worker = new Worker();

}



특정 인스턴스의 사용을 동기화 할 것이냐?  혹은 메소드 자체를 동기화 할 것이냐?의 구분의 차이로 인식하면 쉬울 것 같다. 

사소하지만 주의깊게 생각해야 할 점이 있다면, (1) 번의 경우 synchronized 된 부분이 임계 영역이라면, (2) 번의 경우 메소드 전체가 임계영역이 된다는 점이다.





Sample Code


class Worker { private Object obj; private int count=0; public Worker() { } public synchronized void writeData(Object obj) { this.obj = obj; count++; } public Object getObj() { return obj; } public void setObj(Object obj) { this.obj = obj; } @Override public synchronized String toString() { return "Worker [obj=" + obj + ", count=" + count + "]"; } } public class Main { public static void main(String[] args) {

//Worker라는 하나의 인스턴스를 공유할 것이다. Worker worker = new Worker(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub while (true) { try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } worker.writeData("Writer1"); System.out.println("Data Print :"+worker.toString()); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub while (true) { try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } worker.writeData("Writer2"); System.out.println("Data Print :"+worker.toString()); } } }); thread1.start(); thread2.start(); } }



- WriteData 메소드와 toString 메소드를 동기화함에 따라 순차적인 Count 증가 및 Data를 Write할 수 있었다.





=>결과 출력







위의 방법처럼 synchronized 된 메소드를 구성했다면 , 객체를 synchronized 하여 구성하는 것도 쉽게 할 수 있을 것이다.















+ Recent posts