본문 바로가기
학교 강의/자바프로그래밍

자바프로그래밍 총정리 1 - GUI 제외 부분

by hoshi03 2024. 6. 17.

클래스, 객체, 상속, 모듈, 제네릭&컬랙션, 스트림, 스레드, 소켓, 네트워크, jdbc

 

•  상속

 

객체 -  클래스를 실체화 한 것, instance

캡술화 - 외부로부터 객체 보호

클래스 : 객체 틀

상속 - 자식 클래스가 부모 클래스의 속성, 기능을 받아서 확장

 

• 다형성 

같은 이름의 메소드가 클래스나 객체에 따라 다르게 동작하도록 구현

메서드 오버로딩 - 같은 이름, 매개변수 등이 달라서 다르게 동작

메서드 오버라이딩 - 부모 클래스 메서드를 서브 클래스마다 다르게 구현

 

• 상속

new로 서브 클래스 객체 만들면 생성자 호출, 실행 순서가 어캐되냐
서브 클래스 생성자 호출 -> 슈퍼 클래스 생성자 호출 -> 슈퍼 클래스 생성자 실행 - > 서브 생성자 실행

 

서브클래스에서 super 키워드로 슈퍼 클래스 생성자를 호출 가능하다

 

• 업캐스팅

Person 클래스, Student 클래스가 있을때

Student s = new Student();

Person p = s; <- 업캐스팅, 자동타입변환, 슈퍼 클래스 멤버만 접근가능

 

• 다운 캐스팅

!명시적 타입 변환 필요하다

Person p = new Student(); <- 업캐스팅

Studnet s = (Student) p <- 다운캐스팅, 슈퍼클래스 객체를 서브 클래스 타입으로 변환, 타입 명시해줘야된다

 

• instanceOf 연산자

if( a instanceOf Person) 형태로 객체의 타입을 알 수 있다, Person 타입인지 아닌지 T/F로 판단 가능

 

• 동적 바인딩 - 메서드 오버라이딩시 서브 클래스에서 오버라이딩한 메서드가 실행되는 현상, 실행 중에 다형성

오버로딩은 컴파일 시간에 다형성이 실행된다

super.메서드() 형태로 실행하면 super 클래스의 메서드를 실행하는 정적 바인딩이 일어난다

 

• 추상 메서드

선언되어 있으나 구현되지 않은 메서드, abstract 키워드를 붙여서 선언

public abstract String getName();

public abstract void setName(String s);

 

 추상 클래스

abstract로 선언한 클래스, 추상 메서드가 있을수도, 없을수도 있다

추상 클래스 객체는 생성 불가능, 상속해서 사용하는 방식이다

- 추상 클래스 상속 2가지

서브 클래스가 추상 클래스를 상속받고 추상 메서드를 구현하지 않으면 abstract만 붙은 추상 클래스가 된다

서브 클래스가 추상 메서드를 오버라이딩해서 구현하면 추상 클래스가 아닌, 객체로 생성 가능한 클래스가 된다

- 추상 클래스의 용도

 설계와 구현 분리, 슈퍼 클래스에서는 개념을 정의하고 서브클래스마다 목적에 맞게 슈퍼 클래스의 추상 메서드를 구현

계층적인 상속 관계를 갖는 클래스 구조를 만들때 사용한다

 

• 인터페이스

클래스가 구현해야할 메서드들이 선언되는 추상형, java8 기준 상수,추상메서드,디폴트메서드만 선언 가능하다

- 인터페이스 구성 요소

상수, 추상메서드, defalut 메서드, private메서드, static 메서드 등이 있다

인터페이스는 직접 객체 생성이 불가능하고 PhoneInterace galaxy; 형태로  레퍼런스 변수로는 선언 가능하다

인터페이스를 상속받는 클래스는 인터페이스의 모든 추상메서드를 반드시 구현해야한다

-인터페이스는 여러개를 상속 받을 수 있고, extends 키워드로 다른 인터페이스를 상속 받을 수 있다

 

인터페이스를 상속받고 새로운 추상 메서드를 추가한 인터페이스를 만들 수 있다

interface MobilePhoneInterface extends PhoneInterface { 
	void sendSMS();     // 새로운 추상 메소드 추가
	void receiveSMS();  // 새로운 추상 메소드 추가 
}

 

여러 인터페이스를 상속받고 새로운 추상 메서드를 추가한 인터페이스를 만들 수도 있다

interface MusicPhoneInterface extends MobilePhoneInterface, MP3Interface { 
	void playMP3RingTone(); // 새로운 추상 메소드 추가
}

 

- 인터페이스의 목적

인터페이스는 클래스들이 서로 다른 기능을 구현할 수 있게 하는 다형성을 실행하는 도구이다

 

• 추상 클래스와 인터페이스 비교

유사점 : 객체로 생성 불가능, 상속을 위해 사용, 클래스의 다형성을 실현

 

차이점은 아래 표를 참고

 

• 패키지

패키지가 다르면 같은 이름 클래스도 서로 다른 파일로 취급된다

서로 관련된 클래스와 인터페이스의 컴파일 된 클래스 파일들을 하나의 디렉터리에 묶어 놓은것

- 패키지 사용하기

임포트 없으면 java.util.Scanner scanner = new java.util.Scanner(System.in);

import.java.util.Scanner; 형태로 임포트하면 Scanner scanner = new Scanner(System.in); 형태로 사용

- 패키지 선언

소스파일 첫 줄에

package UI; 형태로 선언

public class Test{ ~ 형태로 클래스를 작성하면 클래스의 경로명은 UI.Test가 된다

다른 클래스에서 위의 Test 클래스를 사용하려면 import UI.Test; 를 하고 Test t = new Test(); 형태로 사용 가능하다

- 디폴트 패키지 

package 선언없이 만든 클래스 패키지, 디폴트 패키지는 현재 디렉터리

 

• 모듈 

java9부터 도입된 여러 패키지와 이미지 등의 자원을 모아 놓은 컨테이너

자바 API를 여러 모듈로 분할해서 프로그램에 필요한 모듈만 가져다 쓸 수 있다

모듈이름.jmod 파일

- 모듈 파일로 부터 모듈 푸는 방법

jmod extract "디렉터리\java.base.jmod"

-모듈 기반 자바 실행환경

jre - 자바 실행환경, 자바 모듈, jvm 등으로 구성

java8까지는 모듈이 없어서 rt.jar 라는 단일체에 모든 api 패키지가 들어있는걸 가져다가 썻음

java9부터는 모듈에서 필요한 것만 가져다가 쓰기 가능

- 모듈화의 목적 : 자바 컴포넌트를 필요에 따라 조립해서 사용, 불필요한 모듈은 로드 안하게 해서 성능 향상

- 주요 패키지

java.lang - string, 수학함수, 입출력 등 기본적으로 필요한 패키지, 자동임포트

java.util - 날짜,시간,벡터,해시맵 등 같이 다양한 유틸 클래스와 인터페이스

java.io - 키/마,프린터,디스크 등 주변기기에 입출력하는 클래스와 인터페이스

java.awt - gui 프로그래밍 관련 클래스와 인터페이스

javax.swing - 스윙 gui 패키지

 

• Object 클래스

- 모든 클래스의 수퍼 클래스

toString() <- Object의 toString()을 재정의해서 개발자가 원하는 것으로 출력 가능

 

• 래퍼 클래스

Integer, Long, Character, Byte, Short, Float, Double, Boolean, Integer 8개, 스트링은 따로

Integer i  = Integer.valueOf(2400); <- 기본 타입 값으로 래퍼 객체 생성

int res =  i.intValue(); <- 래퍼 객체로부터 기본 타입 값 알아내기

Integer.parseInt(25521); <- 문자열을 기본 데이터 타입으로 변환

Integer.toString(123); <- 기본 타입을 문자열로 변환

 

!Float 객체는 double 타입 값으로 생성 가능

Float f = Float.valueOf((double) 3.14);

 

• 박싱 & 언박싱

박싱 : 기본 타입 값을 래퍼 객체로 변환 Integer ten = Integer.valueof(10)l

언박싱 : 래퍼 객체 값을 빼내는거 int i = ten.intValue();

 

jdk 1.5부터는 자동으로 박싱&언박싱이 된다

Integer.ten = 10; <- 자동 박싱 Integer.valueOf

int n = 10; <- 자동 언박싱 intvalue

 

• String 

String s = "hello"; - 스트링 리터럴로 생성 jvm이 리터럴 관리

String s = new String("hola"); String 객체로 생성, 힙 메모리에 String 객체 생성

스트링은 불변, 비교시 equals 사용

concat 으로 스트링 붙이면 새로운 스트링 객체가 생성되는 것

s.trim() <- 공백 제거

 

•StringBuffer -<- 가변 문자열 저장 클래스

•StringTokenizer - 문자열을 여러 문자열로 분리

 

query를 "&" 기준으로 토큰 3개로 분리시키기

String query = "name=kitae&addr=seoul&age=21"; 
StringTokenizer st = new StringTokenizer(query, "&");

 

 

어디서 많이 본 형태

while(st.hasMoreTokens()){
	sout(st.nextToken());
}

 

• Math 클래스로 난수 만들기

random은 0.0 ~ 1.0 사이 double 값을 반환

for(int x=0; x<10; x++) {
	int n = (int)(Math.random()*100 + 1); // n은 [1~100] 사이의 랜덤 정수 
	System.out.println(n);
}

 

Calendar 클래스 - java.util 패키지 안에 있음, 추상 클래스이므로 new로 생성은 불가

 

- 캘린더 객체에서 현재 날짜, 시간 알아내기

Calendar now = Calendar.getInstance();
int year = now.get(Calendar.YEAR);   // now에 저장된 년도 
int month = now.get(Calendar.MONTH) + 1; // now에 저장된 달

 

- 캘린더 객체에 날짜, 시간 설정하기

Calendar firstDate = Calendar.getInstance(); // 캘린더 객체에서 현재 날짜와 시간 가져오기
firstDate.clear(); // 현재 날짜와 시간 정보를 모두 지운다. 
firstDate.set(2016, 11, 25); // 2016년 12월 25일. 12월은 11로 설정 
firstDate.set(Calendar.HOUR_OF_DAY, 20); // 저녁 8시로 설정 
firstDate.set(Calendar.MINUTE, 30); // 30분으로 설정

 

• 컬렉션

객체 컨테이너, 동적으로 크기 조정, 컬렉션은 제네릭으로 구현됨

컬렉션은 자동 박싱/언박싱 되어서 기본 타입 값 사용 가능

 

•제네릭

Stack<Integer> ArrayList<String> 등 매개개변수에 객체 타입 넣어서 사용 가능

 

• 자바 타입 추론 기능

java7 이전 Vector<Integer> v = new Vector<Integer>();

java 7 이후 Vector<Integer> v = new Vector<>(); <- <>연산자에 타입 매개변수 생략

java10 이후 var v = new Vector<Integer>(); <- 컴파일러가 지역 변수 타입 추론 가능

 

• ArrayList와 vector 차이

vector는 스레드 동기화로 멀티 스레딩 환경에서 사용 가능하나

ArrayList는 개발자가 스레드 동기화 코드 사용해야한다

 

• Iterator<E> 인터페이스

컬렉션의 순차 검색을 위해서 사용, 벡터,리스트 등이 상속받는 인터페이스

이터레이터로 인덱스 없이 순차적 검색 가능

Vector<Integer> v = new Vector<Integer>(); 
Iterator<Integer> it = v.iterator();
while(it.hasNext()) { 
	int n = it.next(); 
}

 

• 해쉬맵

key, value 쌍으로 구성되는 요소를 다루는 컬렉션

키를 이용해 값 검색 가능

 

-해쉬맵을 set 이터레이터로 순회하기 <- map.keySet(); 으로 키들을 가져오기 가능

HashMap<String, Integer> map = new HashMap<String, Integer>();
//맵에 원소 넣은 후 set에 map의 keySet으로 키 넣어주기
Set<String> keys = map.keySet();
Iterator<String> it = keys.iterator(); 
while(it.hasNext()) {
	String name = it.next();
	int score = javaScore.get(name); 
	System.out.println(name + " : " + score);
}

 

• Collections 클래스 

java.util 패키지에 있음, 컬렉션으로 연산하고 결과 리턴

sort, reverse, max, min, binarysearch 메서드 등

 

• 제네릭 클래스, 인터페이스

클래스 선언부에 일반화된 타입 추가

public class MyClass<T> {
	T val;
	void set(T a) { 
    val = a; 
    }
	T get() { 
    	return val; 
    }
}

 

위 클래스를 사용하려면 구체화해서 사용

MyClass<String> s = new MyClass<String>(); // 제네릭 타입 T에 String 지정
s.set("hello");
System.out.println(s.get()); // "hello" 출력

 

 제네릭 메서드

import java.io.*;

public class Main {
    static class GStack<T> {
        int top;
        Object [] st;
        public GStack() {
            top = 0;
            st = new Object [10];
        }
        public void push(T item) {
            if(top == 10) return;
            st[top] = item; top++;
        }
        public T pop() {
            if(top == 0) return null;
            top--;
            return (T) st[top];
        }
    }
    public static <T> GStack<T> reverse(GStack<T> a) {
        GStack<T> s = new GStack<T>();
        while (true) {
            T tmp;
            tmp = a.pop();
            if (tmp == null) break;
            else
                s.push(tmp); // 새 스택에 요소를 삽입
        }
        return s; // 새 스택을 반환
    }


    public static void main(String[] args) throws IOException {
        GStack<Double> gs = new GStack<Double>();
        for (int i=0; i<5; i++) gs.push(new Double(i));
        gs = reverse(gs);
        for (int i=0; i<5; i++) System.out.println(gs.pop());
    }
}

 

 

public static <T> GStack<T> reverse(GStack<T> a) 형태로 선언하는 이유

public static GStack<T> reverse(GStack<T> a) 로 선언하면 컴파일러가 T가 무슨 타입인지 알 수가 없다

Gstack의 pop 부분도 (T)로 타입을 캐스팅해서 반환되는 원소가 어떤 타입인지 명시한다

 

 제네릭의 장점 : 컴파일 시 타입 결정, 런타입 충돌 문제 방지, ClassCastException 방지  

 

• 스트림

- 스트림의 입출력은 버퍼를 가지고 순차적으로 이루어진다

입력 스트림, 출력 스트림은 단방향, 둘다 동시에 하는 경우는 없다

• 바이트 스트림과 문자 스트림

바이트 스트림 - 입출력 되는 데이터를 바이트로 처리, 바이너리 파일 읽기 등

문자 스트림 - 문자만 입출력, txt 파일 읽기 등, 문자 1개에 2바이트

 

• 문자 스트림으로 txt파일 읽기

 

파일의 끝을 만나면 read()가 -1을 리턴

public class test {
    public static void main(String[] args) {
        FileReader fin = null;
        {
            try {
                fin = new FileReader("c:\\test.txt");
                int c;
                while ((c = fin.read()) != -1) {
                    System.out.print((char) c);
                }
                fin.close();
            } catch (IOException e){
                System.out.println("오류");
            }
        }
    }
}

 

• 문자 집합 + 인풋스트림리더로 텍스트 파일 읽기

FileInputStream fin = new FileInputStream("c:\\Temp\\hangul.txt"); 
InputStreamReader in = new InputStreamReader(fin, "MS949");

 

in.getIncodeing으로 인코딩을 가져올 수 있다 MS949는 한글 인코딩

public class test {
    public static void main(String[] args) {
        InputStreamReader in = null;
        FileInputStream fin = null;
        try {
            fin = new FileInputStream("c:\\TEMP\\TEST.txt");
            in = new InputStreamReader(fin, "MS949");
            int c;
            System.out.println("인코딩 문자 집합은 " + in.getEncoding());
            while ((c = in.read()) != -1) System.out.print((char)c);
        in.close();
        fin.close();
        }
        catch (IOException e) {System.out.println("입출력 오류");}
    }
}

 

• 문자 출력 스트림 생성하기

FileWriter fout = new FileWriter("c:\\Temp\\test.txt");

 

- 문자 단위로 쓰기

FileWriter fout = new FileWriter("c:\\Temp\\test.txt"); 
fout.write(‘A’); // 문자 ‘A’ 출력
fout.close();

 

- 블록 단위로 쓰기

char [] buf = new char [1024]; // buf[] 배열의 처음부터 배열 크기(1024개 문자)만큼 쓰기 
fout.write(buf, 0, buf.length);

 

- c/Temp에 있는 test.txt에 키보드 입력 저장하는 프로그램

public class test {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        FileWriter out = null;
        int c;
        try {
            out = new FileWriter("c:\\Temp\\test.txt");
            while(true) {
                String line = scanner.nextLine();
                if(line.length() == 0) break;
                out.write(line, 0, line.length());
                out.write("\r\n", 0, 2);
            }
            out.close();
        }
        catch (IOException e) {System.out.println("입출력 오류");}
    }
}

 

• 바이트 스트림 클래스

-바이트 단위의 바이너리 값을 읽고 쓴다

FileOutputStream fout = new FileOutputStream("c:\\Temp\\test.out");
byte b[] = {7,51,3,4,-1,24}; for(int i=0; i<b.length; i++)
fout.write(b[i]); // 바이트 값을 그대로 기록
fout.close();

 

• 버퍼 입출력 스트림

버퍼를 가진 스트림, 입출력 데이터를 버퍼에 일시적으로 저장해서 입출력 효율이 ㄱㅊ아진다

- 버퍼 입출력을 이용하면 입출력시 운영체제의 api 호출 횟수를 줄여 입출력 성능이 개선된다

- 여러번 출력되는 걸 버퍼에 두고 한번에 출력한다

 

• 바이트 버퍼 스트림

-20바이트 크기의 버퍼를 만들어서 모아두었다가 출력한다

BufferedOutputStream bout = new BufferedOutputStream(System.out, 20); 
FileReader fin = new FileReader("c:\\windows\\system.ini");

int c;
while ((c = fin.read()) != -1) { 
	bout.write((char)c);
}
fin.close(); 
bout.close()

 

• File 클래스

java.il.File

파일과 디렉터리 경로명, 파일 관리 기능 등이 있다

File 객체는 파일 읽고 쓰기 못한다!

 

- 파일 객체 생성

File f = new File("c:\\windows\\system.ini");

 

- 파일 경로명 가져오기

String filename = f.getName(); // "system.ini"
String path = f.getPath(); // "c:\\windows\\system.ini"
String parent = f.getParent(); // "c:\\windows"

 

-파일인지 디렉토리인지 구분

if(f.isFile()) // 파일인 경우
System.out.println(f.getPath() + "는 파일입니다."); 
else if(f.isDirectory()) // 디렉터리인 경우
System.out.println(f.getPath() + "는 디렉터리입니다.");

 

- 서브 디렉토리 리스트 가져오기

File f = new File("c:\\Temp");
File[] subfiles = f.listFiles(); // c:\Temp 파일 및 서브디렉터리 리스트 얻기
for(int i=0; i<filenames.length; i++) {
	System.out.print(subfiles[i].getName()); // 파일명 출력
	System.out.println("\t파일 크기: " + subfiles[i].length()); // 크기 출력 
}

 

 파일 클래스와 문자 스트림을 이용해서 파일 내용 덮어쓰기 

import java.io.*;
public class TextCopyEx {
    public static void main(String[] args){
        File src = new File("c:\\windows\\system.ini"); // 원본 파일 경로명
        File dest = new File("c:\\Temp\\system.txt"); // 복사 파일 경로명
        int c;
        try {
            FileReader fr = new FileReader(src);
            FileWriter fw = new FileWriter(dest);
            while((c = fr.read()) != -1) { // 문자 하나 읽고
                fw.write((char)c); // 문자 하나 쓰고
            }
            fr.close(); fw.close();
            System.out.println(src.getPath()+ "를 " + dest.getPath()+ "로 복사하였습니다."); } catch (IOException e) {
            System.out.println("파일 복사 오류"); }
    } 
}

 

• 스레드  & 멀티태스킹

스레드 : 사용자가 작성한 코드, JVM에 의해 스케줄링 되어 실행되는 단위

자바의 멀티스레딩 : 한 스레드가 대기하는 동안 다른 스레드를 실행해서 프로그램 시간 지연을 줄인다

JVM과 멀티스레드의 관계 : 한 jvm은 한 프로그램을 실행, jvm이 스레드를 스케줄링, 한 프로그램은 하나 이상의 스레드로 구구성

한 jvm이 여러개의 스레드를 관리하고 스케줄링해서 실행한다

 

• 자바에서 스레드 만들기

java.lang.Thread 클래스나 java.lang.Runnable 인터페이스로 스레드를 생성

 

 Thread 클래스를 이용한 스레드 생성

스레드 클래스를 상속받은 클래스를 생성하고 run()메소드를 오버라이딩해서 스레드 코드 작성, run으로 스레드 실행

 

아래처럼 Thread를 상속받은 클래스를 작성하고 run 메서드를 오버라이딩해서 스레드 코드를 작성한다

다른데서 스래드 만든 클래스 불러다가 start() 메서드로 실행해주면 된다

class TimerThread extends Thread {
@Override
public void run() { // run() 오버라이딩
	}
}

스레드 실행할 함수{
	TimerThread th = new TimerThread(); 
    th.start();
}

 

 

- 스레드를 이용해서 1초 단위로 n을 1씩 증가시키는 코드

run 부분에 스레드 코드를 작성하고 무한루프를 돌면서 n++-> 1초 sleep -> n++ 을 반복한다

class TimerThread extends Thread {
    private JLabel timerLabel;
    public TimerThread(JLabel timerLabel) {
        this.timerLabel = timerLabel;
    }
    @Override
    public void run() {
        int n=0;
        while(true) {
            timerLabel.setText(Integer.toString(n));
            n++;
            try {
                Thread.sleep(1000);
            }
            catch(InterruptedException e) {
                return;
            }
        }
    }
}

 

위에서 만든 초당 1씩 증가하는 스레드를 gui로 보여주는 코드

TimerThread 스레드 객체를 생성하고 start로 스레드를 실행한다

public class Test extends JFrame {
    public Test() {
        setTitle("Thread를 상속받은 타이머 스레드 예제");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container c = getContentPane();
        c.setLayout(new FlowLayout());
        JLabel timerLabel = new JLabel();
        timerLabel.setFont(new Font("Gothic", Font.ITALIC, 80)); c.add(timerLabel);
        TimerThread th = new TimerThread(timerLabel);
        setSize(300,170);
        setVisible(true);
        th.start();
    }

    public static void main(String[] args) {
        new Test();
    }
}

 

! 스레드 주의사항

run() 메서드를 종료하면 스레드가 종료된다. 스레드를 계속 살아있게 하려면 run()메서드 내 무한루프를 작성

한번 종료된 스레드는 다시 시작할 수 없고 다시 스레드 객체를 생성해서 start()로 시작시켜야 한다

 

• Runnable 인터페이스로 스레드 만들기

 

runnable 인터페이스를 구현하는 클래스를 작성해서 run 메서드로 스레드 코드를 구현

스레드 쓸 클래스에서 스레드 객체를 생성하고 start()로 메서드를 호출한다

class TimerRunnable implements Runnable {
@Override
public void run() { // run() 메소드 구현
	}
}

스레드 실행할 클래스{
	Thread th = new Thread(new TimerRunnable());
    th.start();
}

 

위의 코드랑 거의 비슷하나 여기서는 스레드 객체를 생성한 후 사용자가 Runnable 인터페이스를 구현한
스레드 클래스를 넣어주고 그 스레드 객체를 실행한다

import javax.swing.*;
import java.awt.*;

class TimerThread implements Runnable {
    private JLabel timerLabel;
    public TimerThread(JLabel timerLabel) {
        this.timerLabel = timerLabel;
    }
    @Override
    public void run() {
        int n=0;
        while(true) {
            timerLabel.setText(Integer.toString(n));
            n++;
            try {
                Thread.sleep(1000);
            }
            catch(InterruptedException e) {
                return;
            }
        }
    }
}

public class Test extends JFrame {
    public Test() {
        setTitle("Thread를 상속받은 타이머 스레드 예제");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container c = getContentPane();
        c.setLayout(new FlowLayout());
        JLabel timerLabel = new JLabel();
        timerLabel.setFont(new Font("Gothic", Font.ITALIC, 80)); c.add(timerLabel);
        TimerThread runnable = new TimerThread(timerLabel);
        Thread th = new Thread(runnable);
        setSize(300,170);
        setVisible(true);
        th.start();
    }

    public static void main(String[] args) {
        new Test();
    }
}

 

• 스레드 정보

 

• 스레드 상태 6가지, JVM에 의해서 상태 관리

 

new - 스레드가 생성되었지만 실행 준비는 되지 않은 상태

runnable - 스레드가 실행중이거나, 실행 준비가 되어서 스케줄링 기다리는 상태

waiting - 스레드 동기화를 위해 사용, wait() 메서드를 호출, 다른 스레드가 notify()나 notifyAll()을 불러주길 대기

timed_waiting - sleep(N)을 호출해서 N 밀리초동안 대기하는 상태

block - 스레드가 I/O 작업을 요청하면 JVM이 스레드를 block 상태로 만든다 

terminated - 스레드가 종료된 상태

 

 

• 스레드 우선순위, 스케줄링

최대 10, 최소 1, 보통 5의 우선순위를 가진다

스레드 우선순위는 프로그램에서 변경 가능하다

main 스레드의 우선순위 값은 초기에 5

스레드는 부모 스레드와 동일한 우선순위 값을 가지고 탄생

JVM의 스케줄링 정책은 우선순위 기반, 동일한 우선순위 스레드는 RR로 스케줄링

 

• main()

JVM은 프로그램 실행할떄 main 스레드를 생성해서 main()메서드를 실행하게 한다

 

- main() 메서드에서 main 스레드 정보 얻는 코드

public class ThreadMainEx {
public static void main(String [] args) {
long id = Thread.currentThread().getId(); // 스레드 ID 얻기
String name = Thread.currentThread().getName(); // 스레드 이름 얻기
int priority = Thread.currentThread().getPriority(); // 스레드 우선순위 값 얻기 
Thread.State s = Thread.currentThread().getState(); // 스레드 상태 값 얻기 
System.out.println("현재 스레드 이름 = " + name);
System.out.println("현재 스레드 ID = " + id);
System.out.println("현재 스레드 우선순위 값 = " + priority); 
System.out.println("현재 스레드 상태 = " + s);
	} 
}

 

결과는 아래처럼 나온다

현재 스레드 이름 = main
현재 스레드 ID = 1
현재 스레드 우선순위 값 = 5 
현재 스레드 상태 = RUNNABLE

 

• 스레드 종료와 타 스레드 강제 종료

 

스스로 종료는 run()메서드에서 return을, 타 스레드 종료는 interrupt() 메서드로 종료시킨다

--스스로 return으로 종료

@Override
public void run() {
while(true) {
	System.out.println(n); // 화면에 카운트 값 출력 n++;
	try {
		sleep(1000);
	}
catch(InterruptedException e){
	return; // 예외를 받고 스스로 리턴하여 종료
}} }

 

-- main 메서드에서 TimerThread interrupt로 강제 종료

public static void main(String [] args) { T
	TimerThread th = new TimerThread(); 
    th.start();
	th.interrupt(); // TimerThread 강제 종료
}

 

스레드 클래스가 flag를 가지고 있고 다른 클래스에서 setter로 플래그의 상태를 바꾸면 run 메서드를 return; 하는 방식도 가능

 

• 스레드 동기화 

멀티스레드 프로그램 작성시 다수 스레드가 공유 데이터에 접근해서 데이터 값이 예상치 못하게 변경될 수 있다

동시 접근 문제 해결책은 스레드를 줄세워서 한 스레드가 공유 데이터로 하는 작업을 끝낼 때 까지 다른 스레드를 대기시키는것

 

- 스레드 동기화 기법

synchronized로 동기화 블록을 지정하고

wait()-notify()로 스레드 실행 순서 제어

 

•synchronized 키워드 

한 스레드가 독점 실행해야 하는 부분 표시, 임계 영역 표기

먼저 실행한 스레드가 모니터(독점 권한)를 소유, 모니터 소유 스레드가 모니터를 내놓을 때 까지 대기

-- syncronized 메서드

synchronized void add() { 
        int n = getCurrentSum(); 
        n+=10;
        setCurrentSum(n); 
    }

 

-- synchronized 블록

n+10 하는 부분을 독점적으로 실행해서 데이터 값 변경 방지

void execute() {
        synchronized(this) {
            int n = getCurrentSum(); 
            n+=10;
            setCurrentSum(n);
        }
    }

 

-- synchronized 메서드를 사용한 공유 집계판, add 메서드가 스레드 최종 결과값이 올바르게 200이 된다

add 메서드에서 동기화 부분을 제거하면 값이 올바르게 나오지 않는다

public class Test {
    public static void main(String [] args) {
        SharedBoard board = new SharedBoard();
        Thread th1 = new StudentThread("kitae", board);
        Thread th2 = new StudentThread("hyosoo", board);
        th1.start();
        th2.start();
    }
}
class SharedBoard {
    private int sum = 0; // 집계판의 합
    synchronized public void add() {
        int n = sum;
        Thread.yield(); // 현재 실행 중인 스레드 양보
        n += 10; // 10 증가
        sum = n; // 증가한 값을 집계합에 기록
        System.out.println(Thread.currentThread().getName() + " : " + sum); }
    public int getSum() { return sum; }
}

class StudentThread extends Thread {
    private SharedBoard board; // 집계판의 주소
    public StudentThread(String name, SharedBoard board) {
        super(name);
        this.board = board;
    }
    @Override
    public void run() {
        for(int i=0; i<10; i++)
            board.add();
    }
}

 

스레드 1이 add()하면 스레드 2는 대기, 스레드 2가 add()하면 스레드 1이 대기한다

 

• producer-consumer 문제

producer - 공유 메모리에 데이터 공급

consumer - 공유 메모리 데이터 소비

생산자와 소비자가 동시에 공유 데이터에 접근하면 문제 발생

 

• wait(), notify(), nofityAll()을 이용한 동기화

wait(), notify(), nofityAll() <- Object의 메서드, 모든 객체가 동기화 객체가 될 수 있음, Synchronized 블록 안에서만 사용가능

동기화 객체 - 두 개 이상의 스레드 동기화에 사용하는 객체

wait - 다른 스레드가 nofity 할때가지 대기

notify - wait 중인 스레드 하나를 깨우고 runnable 상태로 만든다

notifyAll - wait 중인 모든 스래드를 깨우고 runnable 상태로 만든다

 

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class MyLabel extends JLabel {
    int barSize = 0; // 바의 크기
    int maxBarSize;
    MyLabel(int maxBarSize) {
        this.maxBarSize = maxBarSize;
    }
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.MAGENTA);
        int width = (int)(((double)(this.getWidth())) /maxBarSize*barSize);
        if(width==0) return;
        g.fillRect(0, 0, width, this.getHeight());
    }
    synchronized void fill() {
        if(barSize == maxBarSize) {
            try {
                wait();
            } catch (InterruptedException e) { return; }
        }
        barSize++;
        repaint(); // 바 다시 그리기
        notify();
    }
    synchronized void consume() {
        if(barSize == 0) {
            try {
                wait();
            } catch (InterruptedException e)
            { return; }
        }
        barSize--;
        repaint(); // 바 다시 그리기
        notify();
    }
}
class ConsumerThread extends Thread {
    MyLabel bar;
    ConsumerThread(MyLabel bar) {
        this.bar = bar;
    }
    public void run() {
        while(true) {
            try {
                sleep(200);
                bar.consume();
            } catch (InterruptedException e)
            { return; }
        }
    }
}



public class TabAndThreadEx extends
        JFrame {
    MyLabel bar = new MyLabel(100);
    TabAndThreadEx(String title) {
        super(title);
        this.setDefaultCloseOperation
                (JFrame.EXIT_ON_CLOSE);
        Container c = getContentPane();
        c.setLayout(null);
        bar.setBackground(Color.ORANGE);
        bar.setOpaque(true);
        bar.setLocation(20, 50);
        bar.setSize(300, 20);
        c.add(bar);
        c.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e)
            {
                bar.fill();
            }
        });
        setSize(350,200);
        setVisible(true);
        c.requestFocus();
        ConsumerThread th = new
                ConsumerThread(bar);
        th.start(); // 스레드 시작
    }
    public static void main(String[] args) {
        new TabAndThreadEx(
                "아무키나 빨리 눌러 바 채우기");
    }
}

 

 네트워크 

TCP/IP 프로토콜 - 신뢰성 있는 전송

IP - TCP 보다 하위 래뱃 프로토콜, 패킷 교환

IP 주소 192.xxx.xxx.xxx 형태의 32비트 IPv4가 대중적, 내 컴퓨터 ip 주소는 cmd에서 ipconfig 명령어로 가져올 수 있다

포트  - 포트 번호로 통신할 프로그램을 식별한다, 각 프로그램은 하나 이상의 포트를 생성할 수 있다

소켓 - TCP/IP 기반의 통신 프로그램을 작성하도록 지원하는 기능, 양끝단의 소켓을 통해 데이터 교환

 

- 자바에서의 소켓 통신 구조

 

• Socket 클래스 -  java.net 패키지에 포함되어 클라이언트 소켓에 사용함

 

1 클라이언트에서 소켓으로 서버 접속

Socket clientSocket = new Socket("128.12.1.1", 5550);

2 소켓으로 데이터를 전송할 입출력 스트림 생성

BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); //입력
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())); //출력

3 서버로 데이터 전송

out.write("hello"+"\n"); out.flush();

4 서버로 부터 데이터 수신

String line = in.readline();

5 네트워크 접속 종료

clientSocket.close(); 

 

• ServerSocket 클래스 - java.net 패키지에 포함되어 서버 소켓에 사용

1 서버 소켓 생성

ServerSocket serverSocket = new ServerSocket(5550); <- 5550 포트로 들어오는 요청을 기다리는 서버 소켓

2 accept 메서드로 연결 요청이 오면 새로운 소켓 객체를 만들어 클라이언트와 통신

Socket socket = serverSocket.accept();

3 소켓으로 데이터를 전송할 입출력 스트림 생성

BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); //입력
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())); //출력

4 Socket 객체의 인풋,아웃풋 스트림을 이용해서 입출력 데이터 스트림을 생성

 

• 서버 클라이언트 연결 과정

서버가 서버 소켓으로 들어오는 연결 요청을 기다림 (listen)

클라이언트가 서버에게 연결 요청

서버가 연결요청 수락(accept), 다른 클라이언트의 연결을 기다림

 

• 소켓 통신 채팅 프로그램

 

-서버 측 코드

 

처음에 Serversocket을 생성하고 try - catch 구문에서 new ServerSocket(9999)로 9999 포트를 연 서버소켓을 생성한다

accept 가 되면 통신용 소켓인 socket을 이용해서 통신한다

닫을때는 스캐너 -> 통신용 소켓 - > 서버 소켓 순서로 닫는다

먼저 클라이언트의 통신을 종료시키고 서버 소켓을 닫아야 문제가 생기지 않는다

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server {
    public static void main(String[] args) {
        BufferedReader in = null;
        BufferedWriter out = null;
        ServerSocket listener = null;
        Socket socket = null;
        Scanner scanner = new Scanner(System.in); // 키보드에서 읽을 scanner 객체 생성
        try {
            listener = new ServerSocket(9999); // 서버 소켓 생성
            System.out.println("연결을 기다리고 있습니다.....");
            socket = listener.accept(); // 클라이언트로부터 연결 요청 대기
            System.out.println("연결되었습니다.");
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            while (true) {
                String inputMessage = in.readLine(); // 클라이언트로부터 한 행 읽기
                if (inputMessage.equalsIgnoreCase("bye")) {
                    System.out.println("클라이언트에서 bye로 연결을 종료하였음");
                    break; // "bye"를 받으면 연결 종료
                }
                System.out.println("클라이언트: " + inputMessage);
                System.out.print("보내기>> "); // 프롬프트
                String outputMessage = scanner.nextLine(); // 키보드에서 한 행 읽기
                out.write(outputMessage + "\n"); // 키보드에서 읽은 문자열 전송
                out.flush(); // out의 스트림 버퍼에 있는 모든 문자열 전송
            }
        } catch (IOException e) { System.out.println(e.getMessage());
        } finally {
            try {
                scanner.close(); // scanner 닫기
                socket.close(); // 통신용 소켓 닫기
                listener.close(); // 서버 소켓 닫기
            } catch (IOException e) { System.out.println("클라이언트와 채팅 중 오류가 발생했습니다."); }
        }
    }
}

 

import java.io.*;
import java.net.*;
import java.util.*;
public class Client {
    public static void main(String[] args) {
        BufferedReader in = null;
        BufferedWriter out = null;
        Socket socket = null;
        Scanner scanner = new Scanner(System.in); // 키보드에서 읽을 scanner 객체 생성
        try {
            socket = new Socket("localhost", 9999); // 클라이언트 소켓 생성. 서버에 연결
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            while (true) {
                System.out.print("보내기>> "); // 프롬프트
                String outputMessage = scanner.nextLine(); // 키보드에서 한 행 읽기
                if (outputMessage.equalsIgnoreCase("bye")) {
                    out.write(outputMessage+"\n"); // "bye" 문자열 전송
                    out.flush();
                    break; // 사용자가 "bye"를 입력한 경우 서버로 전송 후 실행 종료
                }
                out.write(outputMessage + "\n"); // 키보드에서 읽은 문자열 전송
                out.flush(); // out의 스트림 버퍼에 있는 모든 문자열 전송
                String inputMessage = in.readLine(); // 서버로부터 한 행 수신
                System.out.println("서버: " + inputMessage);
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                scanner.close();
                if(socket != null) socket.close(); // 클라이언트 소켓 닫기
            } catch (IOException e) {
                System.out.println("서버와 채팅 중 오류가 발생했습니다.");
            }
        }
    }
}

 

• 수식 계산 서버- 클라이언트

- 서버 코드

공백으로 구분되어 들어오는 입력에 따라 사칙연산, 나머지는 채팅 예제와 거의 비슷하다

import java.io.*;
import java.net.*;
import java.util.*;
public class Server {
    public static String calc(String exp) {
        StringTokenizer st = new StringTokenizer(exp, " ");
        if (st.countTokens() != 3) return "error";
        String res="";
        int op1 = Integer.parseInt(st.nextToken());
        String opcode = st.nextToken();
        int op2 = Integer.parseInt(st.nextToken());
        switch (opcode) {
            case "+": res = Integer.toString(op1 + op2);
                break;
            case "-": res = Integer.toString(op1 - op2);
                break;
            case "*": res = Integer.toString(op1 * op2);
                break;
            default : res = "error";
        }
        return res;
    }
    public static void main(String[] args) {
        BufferedReader in = null;
        BufferedWriter out = null;
        ServerSocket listener = null;
        Socket socket = null;
        try {
            listener = new ServerSocket(9999); // 서버 소켓 생성
            System.out.println("연결을 기다리고 있습니다.....");
            socket = listener.accept(); // 클라이언트로부터 연결 요청 대기
            System.out.println("연결되었습니다.");
            in = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()));
            out = new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream()));
            while (true) {
                String inputMessage = in.readLine();
                if (inputMessage.equalsIgnoreCase("bye")) {
                    System.out.println("클라이언트에서 연결을 종료하였음");
                    break; // "bye"를 받으면 연결 종료
                }
                System.out.println(inputMessage); // 받은 메시지를 화면에 출력
                String res = calc(inputMessage); // 계산. 계산 결과는 res
                out.write(res + "\n"); // 계산 결과 문자열 전송
                out.flush();
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                if(socket != null) socket.close(); // 통신용 소켓 닫기
                if(listener != null) listener.close(); // 서버 소켓 닫기
            } catch (IOException e) {
                System.out.println("클라이언트와 채팅 중 오류가 발생했습니다.");
            }
        }
    }
}

 

import java.io.*;
import java.net.*;
import java.util.*;
public class Client {
    public static void main(String[] args) {
        BufferedReader in = null;
        BufferedWriter out = null;
        Socket socket = null;
        Scanner scanner = new Scanner(System.in);
        try {
            socket = new Socket("localhost", 9999);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            while (true) {
                System.out.print("계산식(빈칸으로 띄어 입력,예:24 + 42)>>"); // 프롬프트
                String outputMessage = scanner.nextLine(); // 키보드에서 수식 읽기
                if (outputMessage.equalsIgnoreCase("bye")) {
                    out.write(outputMessage+"\n"); // "bye" 문자열 전송
                    out.flush();
                    break; // 사용자가 "bye"를 입력한 경우 서버로 전송 후 연결 종료
                }
                out.write(outputMessage + "\n"); // 키보드에서 읽은 수식 문자열 전송
                out.flush();
                String inputMessage = in.readLine(); // 서버로부터 계산 결과 수신
                System.out.println("계산 결과: " + inputMessage);
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                scanner.close();
                if(socket != null) socket.close(); // 클라이언트 소켓 닫기
            } catch (IOException e) {
                System.out.println("서버와 채팅 중 오류가 발생했습니다.");
            }
        }
    }
}