서른여섯 번째 수업

정혅·2024년 4월 3일

더 조은 아카데미

목록 보기
40/76

오전문제

  1. println.txt에 아래 문장이 들어가도록 하시오 (입출력 문제 3)

제 소개를 하겠습니다.
저는 자바 프로그래머입니다.
나이 24, 몸무게 72kg입니다.

package com.test.memo;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;//다양한 형태의 데이터를 문자열의 형태로 출력하거나(println), 문자열의 형태로 조합하여 출력한다(printf)

class MyInfo {
    String info;

    public MyInfo(String info) {
        this.info = info;
    }

    public String toString() {
        return info;
    }
}

public class Practice1 {
    public static void main(String[] args) throws Exception {

        MyInfo mInfo = new MyInfo("저는 자바 프로그래머입니다.");
        String fileName = "println.txt";
        try (OutputStream o = new FileOutputStream(fileName); PrintStream out = new PrintStream(o)) {
            out.println("제 소개를 하겠습니다.");
            out.println(mInfo);
            out.printf("나이 %d, 몸무게 %dkg입니다.", 24, 72);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 원래 콘솔은 문자열만 출력이 가능하다. 따라서 정수나 실수를 출력하려면 문자열로 변환해야 하는데, 이러한 변환을 PrintStream이 대신해 줬던 것이다.

35일차 입출력문제 4, 5, 6 참조

전화번호부 8단계

입력된 데이터를 파일에 저장하고, 프로그램이 다시 실행되면 파일에 저장된 데이터를 프로그램상으로 복원하는 기능을 추가

내가 풀다 실패

package com.test.memo;

import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Iterator;
import java.util.Scanner;
import java.util.TreeSet;

class PhoneInfo implements Comparable<PhoneInfo> {
    private String name;
    private String phoneNumber;

    public PhoneInfo(String name, String phoneNumber) {
        this.name = name;
        this.phoneNumber = phoneNumber;
    }

    public String getName() {
        return name;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void printCurrentState() {
        System.out.println("이름 : " + name);
        System.out.println("전화번호 : " + phoneNumber);
    }

    @Override
    public int compareTo(PhoneInfo pInfo) {
        return name.compareTo(pInfo.name);
    }

    public void saveFile() {
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Personal.ser"))) {
            out.writeObject(name);
            out.writeObject(phoneNumber);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class PhoneUnivInfo extends PhoneInfo {
    private String major;
    private int year;

    public PhoneUnivInfo(String name, String phoneNumber, String major, int year) {
        super(name, phoneNumber);
        this.major = major;
        this.year = year;
    }

    @Override
    public void printCurrentState() {
        super.printCurrentState();
        System.out.println("전공 : " + major);
        System.out.println("학년 : " + year);
    }

    @Override
    public void saveFile() {
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Personal.ser"))) {
            out.writeObject("PhoneUnivInfo");
            super.saveFile();
            out.writeObject(major);
            out.writeObject(year);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

class PhoneCompanyInfo extends PhoneInfo {

    private String company;

    public PhoneCompanyInfo(String name, String phoneNumber, String company) {
        super(name, phoneNumber);
        this.company = company;
    }

    @Override
    public void printCurrentState() {
        super.printCurrentState();
        System.out.println("회사 : " + company);
    }

    @Override
    public void saveFile() {
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Personal.ser"))) {
            out.writeObject("PhoneUnivInfo");
            super.saveFile();
            out.writeObject(company);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class PhoneBook {

    private static PhoneBook pb;
    private TreeSet<PhoneInfo> set;

    private PhoneBook() {
        set = new TreeSet<PhoneInfo>();
    }

    public static PhoneBook getPhoneBook() {
        if (pb == null)
            pb = new PhoneBook();
        return pb;
    }

    public boolean insertPhoneInfo(PhoneInfo phoneInfo) {
        return set.add(phoneInfo);
    }

    public boolean searchPhoneInfoByName(String name) {
        PhoneInfo pInfo = null;
        Iterator<PhoneInfo> itr = set.iterator();
        boolean result = false;

        while (itr.hasNext()) {
            pInfo = itr.next();
            if (pInfo.getName().equals(name)) {
                pInfo.printCurrentState();
                result = true;
            }
        }

        return result;
    }

    public boolean deletePhoneInfoByPhoneNumber(String phoneNumber) {
        PhoneInfo pInfo = null;
        Iterator<PhoneInfo> itr = set.iterator();

        while (itr.hasNext()) {
            pInfo = itr.next();
            if (pInfo.getPhoneNumber().equals(phoneNumber)) {
                itr.remove();
                return true;
            }
        }
        return false;
    }

    public void printAllPhoneInfo() {
        Iterator<PhoneInfo> itr = set.iterator();
        while (itr.hasNext()) {
            itr.next().printCurrentState();
        }
//        for(PhoneInfo info: set)
//            info.printCurrentState();
    }
}

class MenuChoiceException extends Exception {

    public MenuChoiceException(int menu) {
        super(menu + "에 해당하는 선택은 존재하지 않습니다.\n" + "메뉴 선택을 처음부터 다시 진행합니다.");
    }
}

interface Menu {
    int INSERT_PHONE_INFO = 1; // 상수로 준 이유 : 가독성을 높이기 위해서
    int SEARCH_PHONE_INFO = 2;
    int DELETE_PHONE_INFO = 3;
    int SHOW_ALL_PHONE_INFO = 4;
    int QUIT_PHONE_INFO = 5;
}

class PhoneBookUI {
    private PhoneBook pb;
    public static Scanner sc = new Scanner(System.in);

    public PhoneBookUI() {
        this.pb = PhoneBook.getPhoneBook();
        loadDataFromFile();
    }

    private void loadDataFromFile() {
        try (ObjectInput in = new ObjectInputStream(new FileInputStream("Personal.ser"))) {
            while (true) {
                try {
                    Object obj = in.readObject();
                    if (obj instanceof PhoneInfo) {
                        pb.insertPhoneInfo((PhoneInfo) obj);
                    }

                } catch (EOFException | ClassNotFoundException e) {
                    break; // 파일의 끝에 도달하면 종료
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void printMenu() {
        System.out.println("선택하세요...");
        System.out.println("1. 데이터 입력");
        System.out.println("2. 데이터 검색");
        System.out.println("3. 데이터 삭제");
        System.out.println("4. 모든 데이터 보기");
        System.out.println("5. 프로그램 종료");
        System.out.println("선택 : ");
    }

    public void inputMenu() {
        System.out.println("데이터 입력을 시작합니다.");
        System.out.println("1. 일반, 2. 대학, 3. 회사");
        System.out.print("선택 >>");
    }

    public void inputPhoneInfo(int menu) {
        String name, phoneNumber, major, company;
        int year = 0;
        boolean result;
        PhoneInfo phoneInfo = null;

        System.out.println("데이터 입력을 시작합니다.");
        System.out.println("이름 : ");
        name = sc.nextLine();
        System.out.println("전화번호 : ");
        phoneNumber = sc.nextLine();

        if (menu == 1) {
            phoneInfo = new PhoneInfo(name, phoneNumber);
            phoneInfo.saveFile();
        }

        else if (menu == 2) {
            System.out.println("전공 : ");
            major = sc.nextLine();
            System.out.println("학년 : ");
            year = sc.nextInt();
            phoneInfo = new PhoneUnivInfo(name, phoneNumber, major, year);
            phoneInfo.saveFile();
        } else if (menu == 3) {
            System.out.println("회사 : ");
            company = sc.nextLine();
            phoneInfo = new PhoneCompanyInfo(name, phoneNumber, company);
            phoneInfo.saveFile();
        }
        result = pb.insertPhoneInfo(phoneInfo);
        if (result == false)
            System.out.println("이미 등록된 데이터 입니다.");
        else
            System.out.println("데이터 입력이 완료되었습니다.");

    }

    public void searchPhoneInfoByName() {
        String name;
        System.out.println("검색하시고자 하는 이름을 입력해 주세요.");
        name = sc.nextLine();
        System.out.println("사용자 검색을 시작합니다.");
        if (!pb.searchPhoneInfoByName(name))
            System.out.println("찾으시는 사용자가 없습니다.");
    }

    public void deletePhoneInfoByPhoneNumber() {
        String phoneNumber;
        System.out.println("삭제하시고자 하는 전화번호를 입력해 주세요.");
        phoneNumber = sc.nextLine();
        boolean result = pb.deletePhoneInfoByPhoneNumber(phoneNumber);
        if (result)
            System.out.println("삭제가 완료되었습니다.");
        else
            System.out.println("삭제하시고자 하는 전화번호 정보가 없습니다.");
    }

    public void printAllPhoneInfo() {
        System.out.println("모든 사용자 정보를 출력합니다.");
        pb.printAllPhoneInfo();
    }

    public void quitProgram() {
        System.out.println("프로그램을 종료합니다.");
        sc.close();
    }
}

class Practice2 {

    public static void main(String[] args) {
        int menu = 0;
        PhoneBookUI pbUI = new PhoneBookUI();
        Scanner sc = PhoneBookUI.sc;

        while (true) {
            pbUI.printMenu();
            try {
                menu = sc.nextInt();
                sc.nextLine();

                if (menu < Menu.INSERT_PHONE_INFO || menu > Menu.QUIT_PHONE_INFO) {
                    throw new MenuChoiceException(menu);
                }
                switch (menu) {
                case Menu.INSERT_PHONE_INFO:
                    pbUI.inputMenu();
                    menu = sc.nextInt();
                    sc.nextLine();
                    if (menu < 1 || menu > 3) {
                        throw new MenuChoiceException(menu);
                    }
                    pbUI.inputPhoneInfo(menu);
                    break;
                case Menu.SEARCH_PHONE_INFO:
                    pbUI.searchPhoneInfoByName();
                    break;
                case Menu.DELETE_PHONE_INFO:
                    pbUI.deletePhoneInfoByPhoneNumber();
                    break;
                case Menu.SHOW_ALL_PHONE_INFO:
                    pbUI.printAllPhoneInfo();
                    break;
                case Menu.QUIT_PHONE_INFO:
                    pbUI.quitProgram();
                    return;
                }
            } catch (MenuChoiceException e) {
                System.out.println(e.getMessage());
            }
        }
    }
}
  • 바이트 스트림으로 진행하다보니, 파일을 열면 인코딩이 다 깨져있고, 다시 프로그램으로 불러와 저장되지 않는다.. 이 방법이 아닌것같다.
  1. PhoneBook에 loadData메소드를 생성해 파일의 내용을 불러오게끔 설졍한다.

  2. saveData에 if(set.size() == 0) return ; > 데이터가 다 차면 반환이라는 의미다.

package com.test.memo;

import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Scanner;
import java.util.TreeSet;

class PhoneInfo implements Serializable {
    private String name;
    private String phoneNumber;

    public PhoneInfo(String name, String phoneNumber) {
        this.name = name;
        this.phoneNumber = phoneNumber;
    }

    public String getName() {
        return name;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void printCurrentState() {
        System.out.println("이름 : " + name);
        System.out.println("전화번호 : " + phoneNumber);
    }

    @Override
    public int hashCode() {
        return phoneNumber.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return phoneNumber.equals(((PhoneInfo) obj).phoneNumber);
    }

//    @Override
//    public int compareTo(PhoneInfo pInfo) {
//        return name.compareTo(pInfo.name);
//    }
}

class PhoneUnivInfo extends PhoneInfo {
    private String major;
    private int year;

    public PhoneUnivInfo(String name, String phoneNumber, String major, int year) {
        super(name, phoneNumber);
        this.major = major;
        this.year = year;
    }

    @Override
    public void printCurrentState() {
        super.printCurrentState();
        System.out.println("전공 : " + major);
        System.out.println("학년 : " + year);
    }

}

class PhoneCompanyInfo extends PhoneInfo {

    private String company;

    public PhoneCompanyInfo(String name, String phoneNumber, String company) {
        super(name, phoneNumber);
        this.company = company;
    }

    @Override
    public void printCurrentState() {
        super.printCurrentState();
        System.out.println("회사 : " + company);
    }
}

class PhoneBook {

    private static PhoneBook pb;
//    private TreeSet<PhoneInfo> set;
    private HashSet<PhoneInfo> set;
    private Iterator<PhoneInfo> itr;

    private PhoneBook() {
        loadData();
    }

    void loadData() {
        set = new HashSet<PhoneInfo>();
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("phoneInfo.txt"))) {
            if (in != null) {
                PhoneInfo pInfo = (PhoneInfo) in.readObject();
                while (pInfo != null) {
                    insertPhoneInfo(pInfo);
                    try {
                        pInfo = (PhoneInfo) in.readObject();
                    } catch (EOFException e) {
                        break;
                    }
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static PhoneBook getPhoneBook() {
        if (pb == null)
            pb = new PhoneBook();
        return pb;
    }

    public boolean insertPhoneInfo(PhoneInfo phoneInfo) {
        return set.add(phoneInfo);
    }

    public boolean searchPhoneInfoByName(String name) {
        PhoneInfo pInfo = null;
        Iterator<PhoneInfo> itr = set.iterator();
        boolean result = false;

        while (itr.hasNext()) {
            pInfo = itr.next();
            if (pInfo.getName().equals(name)) {
                pInfo.printCurrentState();
                result = true;
            }
        }

        return result;
    }

    public boolean deletePhoneInfoByPhoneNumber(String phoneNumber) {
        PhoneInfo pInfo = null;
        Iterator<PhoneInfo> itr = set.iterator();

        while (itr.hasNext()) {
            pInfo = itr.next();
            if (pInfo.getPhoneNumber().equals(phoneNumber)) {
                itr.remove();
                return true;
            }
        }
        return false;
    }

    public void printAllPhoneInfo() {
        Iterator<PhoneInfo> itr = set.iterator();
        while (itr.hasNext()) {
            itr.next().printCurrentState();
        }
//        for(PhoneInfo info: set)
//            info.printCurrentState();
    }

    void saveData() {// 데이터 저장
        if (set.size() == 0)
            return;
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("phoneInfo.txt"))) {
            itr = set.iterator();
            while (itr.hasNext()) {
                out.writeObject(itr.next());
            }
        } catch (FileNotFoundException e) {
            System.out.println("파일이 없습니다.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    void quitProgram() {
        saveData();
    }
}

class MenuChoiceException extends Exception {

    public MenuChoiceException(int menu) {
        super(menu + "에 해당하는 선택은 존재하지 않습니다.\n" + "메뉴 선택을 처음부터 다시 진행합니다.");
    }
}

interface Menu {
    int INSERT_PHONE_INFO = 1; // 상수로 준 이유 : 가독성을 높이기 위해서
    int SEARCH_PHONE_INFO = 2;
    int DELETE_PHONE_INFO = 3;
    int SHOW_ALL_PHONE_INFO = 4;
    int QUIT_PHONE_INFO = 5;
}

class PhoneBookUI {
    private PhoneBook pb;
    public static Scanner sc = new Scanner(System.in);

    public PhoneBookUI() {
        this.pb = PhoneBook.getPhoneBook();
    }

    public void printMenu() {
        System.out.println("선택하세요...");
        System.out.println("1. 데이터 입력");
        System.out.println("2. 데이터 검색");
        System.out.println("3. 데이터 삭제");
        System.out.println("4. 모든 데이터 보기");
        System.out.println("5. 프로그램 종료");
        System.out.println("선택 : ");
    }

    public void inputMenu() {
        System.out.println("데이터 입력을 시작합니다.");
        System.out.println("1. 일반, 2. 대학, 3. 회사");
        System.out.print("선택 >>");
    }

    public void inputPhoneInfo(int menu) {
        String name, phoneNumber, major, company;
        int year = 0;
        boolean result;
        PhoneInfo phoneInfo = null;

        System.out.println("데이터 입력을 시작합니다.");
        System.out.println("이름 : ");
        name = sc.nextLine();
        System.out.println("전화번호 : ");
        phoneNumber = sc.nextLine();

        if (menu == 1) {
            phoneInfo = new PhoneInfo(name, phoneNumber);
        }

        else if (menu == 2) {
            System.out.println("전공 : ");
            major = sc.nextLine();
            System.out.println("학년 : ");
            year = sc.nextInt();
            phoneInfo = new PhoneUnivInfo(name, phoneNumber, major, year);
        } else if (menu == 3) {
            System.out.println("회사 : ");
            company = sc.nextLine();
            phoneInfo = new PhoneCompanyInfo(name, phoneNumber, company);
        }
        result = pb.insertPhoneInfo(phoneInfo);
        if (result == false)
            System.out.println("이미 등록된 데이터 입니다.");
        else
            System.out.println("데이터 입력이 완료되었습니다.");
    }

    public void searchPhoneInfoByName() {
        String name;
        System.out.println("검색하시고자 하는 이름을 입력해 주세요.");
        name = sc.nextLine();
        System.out.println("사용자 검색을 시작합니다.");
        if (!pb.searchPhoneInfoByName(name))
            System.out.println("찾으시는 사용자가 없습니다.");
    }

    public void deletePhoneInfoByPhoneNumber() {
        String phoneNumber;
        System.out.println("삭제하시고자 하는 전화번호를 입력해 주세요.");
        phoneNumber = sc.nextLine();
        boolean result = pb.deletePhoneInfoByPhoneNumber(phoneNumber);
        if (result)
            System.out.println("삭제가 완료되었습니다.");
        else
            System.out.println("삭제하시고자 하는 전화번호 정보가 없습니다.");
    }

    public void printAllPhoneInfo() {
        System.out.println("모든 사용자 정보를 출력합니다.");
        pb.printAllPhoneInfo();
    }

    public void quitProgram() {
        System.out.println("프로그램을 종료합니다.");
        sc.close();
    }
}

class Practice2 {

    public static void main(String[] args) {
        int menu = 0;
        PhoneBookUI pbUI = new PhoneBookUI();
        Scanner sc = PhoneBookUI.sc;

        while (true) {
            pbUI.printMenu();
            try {
                menu = sc.nextInt();
                sc.nextLine();

                if (menu < Menu.INSERT_PHONE_INFO || menu > Menu.QUIT_PHONE_INFO) {
                    throw new MenuChoiceException(menu);
                }
                switch (menu) {
                case Menu.INSERT_PHONE_INFO:
                    pbUI.inputMenu();
                    menu = sc.nextInt();
                    sc.nextLine();
                    if (menu < 1 || menu > 3) {
                        throw new MenuChoiceException(menu);
                    }
                    pbUI.inputPhoneInfo(menu);
                    break;
                case Menu.SEARCH_PHONE_INFO:
                    pbUI.searchPhoneInfoByName();
                    break;
                case Menu.DELETE_PHONE_INFO:
                    pbUI.deletePhoneInfoByPhoneNumber();
                    break;
                case Menu.SHOW_ALL_PHONE_INFO:
                    pbUI.printAllPhoneInfo();
                    break;
                case Menu.QUIT_PHONE_INFO:
                    pbUI.quitProgram();
                    return;
                }
            } catch (MenuChoiceException e) {
                System.out.println(e.getMessage());
            }
        }
    }
}
  • phoneInfo에 넣는게 아니라 Phonebook이였어서 다시 해봤는데 또 실패 왜 파일이 안불러와지는거지 생성도 안되네..

자바의 메모리 영역

Method(Static)영역

  • JVM이 동작해서 클래스가 로딩될때 생성
  • 클래스 파일의 바이트 코드, 메소드, 상수, 클래스 변수(static 변수), 메소드 코드 등이 저장되는 공간입니다.
  • JVM이 시작될 때 생성되고, 모든 스레드가 공유하는 영역입니다.
  • Java 8부터는 PermGen 영역이 제거되고, Metaspace로 대체되었습니
public class MyClass {
    private static int myStaticVariable = 10;

    public void myMethod() {
        // 메소드 내용
    }
}

MyClass가 로드될 때, 이 클래스의 정보와 myStaticVariable이 메소드 영역에 할당된다.


Stack영역

참조변수가 저장되는 공간

  • 각 스레드마다 개별적으로 할당되며, 메소드 호출과 관련된 정보를 저장합니다.

  • 스코프의 범위는 중괄호안의 범위인데, hi()메서드 안에서 선언된 지역변수는 중괄호를 벗어나면 다른 메소드나 클래스에서 사용할 수 없고, 스택 메모리에서 사라진다는 의미다.

  • 메소드가 호출될 때마다 해당 메소드에 대한 지역 변수, 매개변수, 리턴 값 및 메소드 호출에 필요한 데이터가 스택 프레임에 저장됩니다.

  • 메소드가 실행을 마치면 해당 스택 프레임이 제거되며, 이러한 스택 구조는 메소드 호출 및 반환에 따라 계속 변합니다.

public void myMethod() {
    int localVar = 5;
    // 메소드 내용
}

public void anotherMethod() {
    myMethod();
    // 다른 작업 수행
}

anotherMethod가 호출되면 스택에 새로운 프레임이 생성되고, 해당 프레임에는 myMethos의 지역변수인 localVar와 해당 메소드의 다른 정보가 저장된다. 메소드 호출이 완료되면 해당 스택 프레임이 제거된다.


Heap영역

객체가 저장되는 공간

  • 객체 인스턴스, 배열 등이 동적으로 생성되는 공간입니다.
  • 프로그램에서 런타임 시간에 생성되는 객체들이 저장되며, Garbage Collection이 이루어져 메모리 관리가 이루어집니다.
  • Young Generation과 Old Generation으로 나뉘며, Young Generation은 새로 생성된 객체들이 할당되는 곳이며, Old Generation은 오랫동안 살아남은 객체들이 있는 곳입니다.
MyClass myObject = new MyClass();

myObject는 힙에 저장되며, 해당 객체의 상태와 메소드가 동적으로 할당된다. 배열도 비슷한 방식으로 힙에 저장된다.


Thread

하나의 프로세스 내부에서 독립적으로 실행되는 하나의 작업 단위를 말하며, 세부적으로는 운영체제에 의해 관리되는 하나의 작업 혹은 태스크를 의미한다.

1. JVM에 의해 하나의 프로세스가 발생하고 main( ) 안의 실행문 들이 하나의 스레드다.

2. main( ) 이외의 또 다른 스레드를 만들려면 Thread 클래스를 상속하거나 Runnable 인터페이스를 구현한다. > 단일 상속만 가능해서 RUnnable 구현을 더 많이 사용

3. 다중 스레드 작업 시에는 각 스레드 끼리 정보를 주고받을 수 있어 처리 과정의 오류를 줄일 수 있다.

4. 프로세스끼리는 정보를 주고받을 수 없다.

Thread의 생성 주기

1. Runnable (준비상태)

스레드가 실행되기 위한 준비단계입니다. CPU를 점유하고 있지않으며 실행(Running 상태)을 하기 위해 대기하고 있는 상태입니다. 코딩 상에서 start( ) 메소드를 호출하면 run( ) 메소드에 설정된 스레드가 Runnable 상태로 진입합니다. “Ready“ 상태라고도 합니다.

2. Running (실행상태)

CPU를 점유하여 실행하고 있는 상태이며 run() 메서드는 JVM만이 호출 가능합니다. Runnable(준비상태)에 있는 여러 스레드 중 우선 순위를 가진 스레드가 결정되면 JVM이 자동으로 run( ) 메소드를 호출하여 스레드가 Running 상태로 진입합니다.

3. Dead (종료상태)

Running 상태에서 스레드가 모두 실행되고 난 후 완료 상태입니다. “Done” 상태라고도 합니다.

4. Blocked (지연상태)

CPU를 점유권을 상실한 상태입니다. 후에 특정 메서드를 실행시켜 Runnable(준비상태)로 전환합니다.

wait( ) 메소드에 의해 Blocked 상태가 된 스레드는 notify( ) 메소드가 호출되면 Runnable 상태로 갑니다. sleep(시간) 메소드에 의해 Blocked 상태가 된 스레드는 지정된 시간이 지나면 Runnable 상태로 갑니다.

Thread이름 관련 메소드


setName()을 이용한 이름 설정

public class MyThread implements Runnable {
   @Override
   public void run() {
       System.out.println("Thread name: " + Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       MyThread myThread = new MyThread();
       Thread thread = new Thread(myThread);
       thread.setName("CustomThreadName"); // 스레드 이름 설정
       thread.start();
   }
}

Thread클래스를 상속받아 생성해서 이름 설정

public class MyThread extends Thread {
    public MyThread(String name) {
        super(name); // 스레드 이름 설정
    }

    @Override
    public void run() {
        System.out.println("Thread name: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        MyThread thread1 = new MyThread("Thread 1");
        MyThread thread2 = new MyThread("Thread 2");

        thread1.start();
        thread2.start();
    }
}

Thread 문제

  1. 프로그램의 실행요청은 컴퓨터 사용자에 의해 이뤄지지만, 실질적인 프로그램의 실행은 ( )에 의해서 이뤄진다.

    • 운영체제

  1. 프로그램의 실행이 요청 되면, 다음의 형태로 메모리 공간이 할당된다.
    메모리에 대해 서술하시오.
  • 메소드영역, 스택 영역, 힙 영역 으로 구성된다.

    • 메소드(메소드의 바이트 코드, static 변수)

      • 클래스의 바이트코드(JVM에 의해 실행 가능한 코드)가 로딩되는 메모리 공간, 인스턴스를 만들거나 클래스 멤버의 접근을 위해서는 어딘가에 클래스의 정보가 저장되어야 하는데, 그 영역이 메소드 영역인 것.
    • 스택 (지역변수, 매개변수)

      • 중괄호 내에 할당된 이후에 해당 중괄호를 벗어나면 바로 소멸되는 특성의 데이터 저장을 위한 영역. 지역변수와 매개변수는 선언되는 순간에 스택에 할당되었다가 자신이 할당된 영역을 벗어나면 소멸이 된다.
    • 힙영역(인스턴스)

      • 인스턴스의 소멸시점 및 소멸 방법이 지역변수와 다르기 때문에 별도의 공간에 할당되는데, 인스턴스의 참조변수는 메소드 내에 있기 때문에 지역변수이지만 참조변수가 가리키는 인스턴스는 힙영역에 저장되는 것.
  • 이렇듯 할당된 메모리 공간을 기반으로 실행 중에 있는 프로그램을 가리켜 ( )라 한다.
    따라서 ( )를 간단히 '실행중인 프로그램'으로 설명하기도 한다.

    • 프로세스

  1. 하나의 프로세스에 둘 이상의 쓰레드를 실행시키자.
    그리고 각각의 쓰레드 이름에 "멋진 쓰레드", 와 "예쁜 쓰레드"라는 이름을 주고 반복을 100번을 시키자
package com.test.memo;

class ShowTread extends Thread {
    String threadName;

    public ShowTread(String name) {
        // super(name); Thread클래스의 getName 메소드를 통해 언제든지 문자열의 형태로 참조할 수 있다.
        threadName = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("안녕하세요. " + threadName + " 입니다.");

            try {
                sleep(100); // ms이므로, 0.1초 의미
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class Practice {
    public static void main(String[] args) {
        ShowTread s1 = new ShowTread("멋진 쓰레드");
        ShowTread s2 = new ShowTread("이쁜 쓰레드");
        s1.start();
        s2.start();
    }
}
  • 인자값으로 문자열을 받은 name을 threadName에 넣어 주소값을 가져 참조시킬 수 있다.

super(name); 으로 참조하려면?

class ShowThread extends Thread
{
    public ShowThread(String name)
    {
        super(name);
    }

    public void run()
    {
        for(int i=0; i<100; i++)
        {
            System.out.println("안녕하세요. "+getName()+"입니다.");
            try
            {
                sleep(100);
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
}

class ThreadUnderstand
{
    public static void main(String[] args)
    {
        ShowThread st1=new ShowThread("멋진 쓰레드");
        ShowThread st2=new ShowThread("예쁜 쓰레드");
        st1.start();
        st2.start();
    }
}
  • 출력하는 부분에 getName()메서드 Thread클래스의 생성자를 실행해 현재 스레드의 이름을 가져와 출력할 수 있다.

  1. 쓰레드의 우선순위를 가져오는 메소드?

    • getPriority()
  2. 쓰레드의 우선순위를 설정하는 메소드?

    • setPriority()
  3. 쓰레드의 우선순위를 설정할 때 쓰는 상수는? > 최소1 ~ 최대 10

    • Thread.MAX_PRIORITY 10 > 최대 우선순위

    • Thread.NORM_PRIORITY 5 > 기본 우선순위

    • Thread.MIN_PRIORITY 1 > 최소 우선순위

      4번 5번 메소드 괄호 안에 넣을 수 있다

      myThread.setPriority(Thread.MAX_PRIORITY); // 쓰레드의 우선순위를 최대 우선순위로 설정


  1. 쓰레드의 라이프 사이클(Life Cycle)은?

    • New 상태

    • Runnable 상태

    • Blocked 상태

    • Dead 상태


  1. class Sum 이 있고 여기에는 숫자를 저장할 수 있는 Instance 변수와 숫자를 더하는 메소드 숫자를 반환하는 메소드가 있다
    class AddThread 라는 클래스를 쓰레드를 돌리기 위해서 인터페이스를 구현해서 두 개의 숫자 인스턴스 변수와
    이 두개의 숫자 인스턴스 변수를 시작 값부터 끝 값까지 더하도록 메소드를 만들자. 그리고 AddThread 클래스는 Sum을 상속받도록 하자.
    그리고 메인메소드에서 쓰레드를 두 개 생성해서 하나는 1부터 50까지 더하고 하나는 51부터 100까지 더해서 두 개의 쓰레드 실행결과 그 더한 값을 출력하도록 하자.

Runnable

class Sum
{
    int num;
    public Sum() { num=0; }
    public void addNum(int n) { num+=n; }
    public int getNum() { return num; }
}

class AdderThread extends Sum implements Runnable
{    
    int start, end;

    public AdderThread(int s, int e)
    {
        start=s;
        end=e;
    }
    public void run()
    {
        for(int i=start; i<=end; i++)
            addNum(i);
    }
}

class RunnableThread
{
    public static void main(String[] args)
    {
        AdderThread at1=new AdderThread(1, 50);
        AdderThread at2=new AdderThread(51, 100);
        Thread tr1=new Thread(at1);
        Thread tr2=new Thread(at2);
        tr1.start();
        tr2.start();

        try
        {
            tr1.join();
            tr2.join();
        }
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }

        System.out.println("1~100까지의 합: "+(at1.getNum()+at2.getNum()));
    }
}
  • join()메서드를 사용하지않으면 0으로 출력된다.

    • 그 이유는 비유를 하자면 문제를 내주고서는 펜을 잡을 시간도 주지않고 문제의 답을 달라고 하는것과 같다.

      1. 특정 쓰레드의 작업이 완료될 때까지 다른 쓰레드가 대기해야하는 경우

      2. 여러 쓰레드가 병렬로 실행되지만 특정 쓰레드의 결과를 다른 쓰레드가 사용해야 하는 경우

      3. 특정 쓰레드의 작업 결과가 필요한 후에 다른 작업을 계속할 수 있는 경우


  1. 해당 쓰레드가 종료될 때까지 실행을 멈출 때 호출하는 메소드는?

    • join() 메소드

Thread 문제 2(Thread 상속)

[쓰레드 클래스의 정의와 쓰레드의 생성]
RunnableThread.java에서는 총 두 개의 쓰레드를 생성해서 각각 1부터 50까지, 그리고 51부터 100까지 덧셈을 진행하게 하고, 그 결과를 취해서 최종적으로 1부터 100까지의 덧셈결과를 출력하였다. 이번에는 이 예제를 Runnable 인터페이스를 구현하는 방식이 아닌, 쓰레드 클래스를 정의하는 방식으로 변경해보자.

package com.test.memo;

class Sum {
    int num;

    Sum() {
        num = 0;
    }

    void addNum(int n) {
        num += n;
    }

    int getNum() {
        return num;
    }
}

class Thd extends Thread {
    int start;
    int end;
    int num;

    Thd(int start, int end) {
        this.start = start;
        this.end = end;
        num = 0;
    }

    void addNum(int n) {
        num += n;
    }

    int getNum() {
        return num;
    }

    public void run() {
        for (int i = start; i < end; i++) {
            addNum(i);
        }
    }

}

class Practice {
    public static void main(String[] args) {
        Thd a1 = new Thd(1, 50);
        Thd a2 = new Thd(51, 100);

        Thread t1 = new Thread(a1);
        Thread t2 = new Thread(a2);

        t1.start();//메모리 공간이 준비가 되면 자동으로 run()메소드 호출 
        t2.start();

        try {
            // 쓰레드 인스턴스를 대상으로 join 메소드를 호출하고 있다. 이는 해당 쓰레드가 종료될 때 까지 실행을 멈출 때 호출하는 메소드이다.
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.print("1에서 50까지의 합 : " + a1.getNum() + "\n");
        System.out.print("51에서 100까지의 합 : " + a2.getNum());
    }
}

Thread 문제 3(동기화)

Increment라 이름의 클래스는 num이라는 인스턴스 변수를 가지고 있고 그 클래스는 1씩 증가시키는 메소드와 숫자 값을 가져오는 메소드가 있다. IncThread라는 클래스는 Increment라는 클래스의 참조변수를 인스턴스변수로 가지고 있고 쓰레드를 실행시키면 중첩된 반복문으로 각각 10000 씩 Increment의 1씩 증가시키는 메소드를 호출한다.
그리고 main메소드가 있는 클래스에서 Increment 인스턴스한개 IncThread 3개를 만들고 3개의 쓰레드를 실행한 후에 Increment의 num의 값을 출력해본다.

동기화 사용 x

class Increment
{
    int num=0;
    public void increment(){ num++; }
//    public synchronized void increment(){ num++; } 동기화처리 

//public void increment()
//{
//    synchronized(this)동기화 블럭 처리 
//    {
//        num++;
//    }
}
    public int getNum() { return num; }
}

class IncThread extends Thread
{    
    Increment inc;

    public IncThread(Increment inc)
    {
        this.inc=inc;
    }
    public void run()
    {
        for(int i=0; i<10000; i++)
            for(int j=0; j<10000; j++)
                inc.increment();
    }
}

class ThreadSyncError
{
    public static void main(String[] args)
    {
        Increment inc=new Increment();
        IncThread it1=new IncThread(inc);
        IncThread it2=new IncThread(inc);
        IncThread it3=new IncThread(inc);

        it1.start();
        it2.start();
        it3.start();

        try
        {
            it1.join();
            it2.join();
            it3.join();
        }
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }

        System.out.println(inc.getNum());
    }
}
  • 결과값은 3억이 넘지 않는다. 그 이유는?

    • num에 있는값을 연산하기위해 cpu로 올리면 num을 ++ 시키니까 +1이되고, num으로 돌아온다. 여기서 메모리에 올려서 연산을하는데 그 사이에 증가되기 전 값을 다른 스레드가 접근을 하게되며 증가값이 반영되지않고 num에 들어가는 원리다.

    이렇게 다른 스레드가 접근하지 못하게끔 막는것이 동기화(synchronized) 다.

  • 위 주석처리된 부분이 동기화처리 시키는 부분인데, 이를 실행시키면 성능이 매우 떨어진다. > 느려짐

    • 동기화 메소드, 동기화 블럭으로 처리할 수 있다.

동기화 메소드

public synchronized void increment()
{
    num++;
}

동기화블럭

public void increment()
{
    synchronized(this)//this객체 주소값을 열쇠로 사용 
    {
        num++;
    }
}
  • 메소드보다 블럭으로 처리하는것이 더 좋다.

  • 동기화 블럭만 다른 스레드들이 접근하지 못하는것이고, 다른 메서드에서 이 동기화블럭은 접근 가능하다.


Thread문제 (key를 사용해 동기화)

1번

class IHaveTwoNum
{
    int num1=0;
    int num2=0;

    public void addOneNum1() 
    {
        synchronized(key1)//키가 한개 
        {
            num1+=1; 
        }
    }
    public void addTwoNum1() 
    { 
        synchronized(key1)
        {
            num1+=2; 
        }
    }    
    public void addOneNum2() 
    { 
        synchronized(key2) //만약 여기가 key1이였다면, 문제발생> num1이 아닌 num2에 증가시키고 있기 때문에(성능 많이 저하)  
        {                   //굳이 접근제한을 걸 필요가 없는것 
            num2+=1; 
        }
    }
    public void addTwoNum2() 
    { 
        synchronized(key2)
        {
            num2+=2; 
        }
    }

    public void showAllNums()
    {
        System.out.println("num1: "+num1);
        System.out.println("num2: "+num2);
    }

    Object key1=new Object();
    Object key2=new Object();
}

class AccessThread extends Thread
{
    IHaveTwoNum twoNumInst;

    public AccessThread(IHaveTwoNum inst)
    {
        twoNumInst=inst;
    }

    public void run()
    {
        twoNumInst.addOneNum1();
        twoNumInst.addTwoNum1();

        twoNumInst.addOneNum2();
        twoNumInst.addTwoNum2();
    }
}

class SyncObjectKeyAnswer
{
    public static void main(String[] args)
    {
        IHaveTwoNum numInst=new IHaveTwoNum();

        AccessThread at1=new AccessThread(numInst);//numInst를 공유하기위해 생성자에 넣었다. 
        AccessThread at2=new AccessThread(numInst);

        at1.start(); //스레드 실행 
        at2.start();

        try
        {
            at1.join(); //첫번째 스레드가 종료될때까지 대기 
            at2.join(); //두 번째 스레드가 종료될때까지 대기 
        }
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }
        numInst.showAllNums();
    }
}
  • 만약 IhaveTwoNum클래스에 있는 동기화 메소드들의 키가 모두 동일하다면, 굉장히 성능이 저하된다.

    • 동기화 안에서 수행하는 변수가 같다면, 키가 동일해도 되지만, 변수가 다르다면, 다른 키를 사용하는게 좋다. > 성능 up

    • 같은키를 쓰면 다른 변수에 증감을 시키는 메소드들도 모두 접근제한이 걸리기 때문이다.

  • 우리 눈에 보이지는 않지만 자바의 모든 인스턴스는 하나의 열쇠가 존재하기때문에 따로 key1 이렇게 줄 필요없이 this로 넣어도 된다.

String Buffer클래스는 쓰레드에 안전
이미 동기화 처리가 되어있어서, 둘 이상의 쓰레드가 동시에 접근을 해도 문제가 발생하지 않는다.

Collection 클래스인 ArrayList, HashSet 클래스는 동기화처리가 되고있지 않다.j

0개의 댓글