Backend
home

2024. 7. 22

복습 - 키워드로 정리

API 도큐먼트
애노테이션
Object 클래스
제네릭

멀티 스레드

하나의 프로세스가 두 가지 이상의 작업을 처리할 수 있는 이유는 멀티 스레드(Multi Thread)가 있기 때문이다.
스레드(Thread)는 코드의 실행 흐름을 말하는데, 프로세스 내에 스레드가 두 개라면 두 개의 코드 실행 흐름이 생긴다는 의미이다.
멀티 프로세스 : 프로그램 단위의 멀티 태스킹 멀티 스레드 : 프로세스 단위의 멀티 태스킹 (프로그램 내부) => ex) 카카오톡에서 동영상을 보내는 동안 메세지 전송
Plain Text
복사
멀티 프로세스는 서로 독립적이므로 하나의 프로세스에서 오류가 발생해도 다른 프로세스에 영향을 미치지 않지만, 멀티 스레드는 하나의 스레드가 예외를 발생시키면, 프로세스가 종료되므로 다른 스레드에게 영향을 미친다.

멀티 스레드 - 메인 스레드, 작업 스레드 생성과 실행

모든 Java 프로그램은 메인 스레드(Main Thread)가 main() 메소드를 실행하면서 시작된다.
메인 스레드는 main() 메소드의 첫 코드부터 순차적으로 실행하고 main( ) 메소드의 마지막 코드를 실행하거나 return 문을 만나면 실행을 종료한다.
멀티 스레드 환경에서는 실행 중인 스레드가 하나라도 있다면 프로세스가 종료되지 않는다.
메인 스레드는 반드시 존재하기 때문에 추가적인 작업 수만큼 스레드를 생성한다.
작업 스레드도 객체로 관리하기 때문에 클래스가 필요하다.
Thread 클래스로 직접 객체를 생성하는 방법
하위 클래스를 만들어 생성하는 방법

멀티 스레드 - Thread 클래스로 직접 스레드 생성하기

java.lang 패키지에는 Thread 클래스로부터 작업 스레드 객체를 직접 생성하기 위해서는 Runnable 구현 객체를 매개값으로 갖는 생성자를 호출하면 된다.
Runnable은 스레드가 작업을 실행할 때 사용하는 인터페이스로, 그 안에는 run( ) 메소드가 정의되어 있다
구현 클래스는 run( ) 메소드에 스레드가 실행할 코드를 재정의(오버라이딩)해주면 된다.
일반적으로는 명시적인 Runnable 구현 클래스를 작성하지 않고, 익명 구현 객체를 통해 작성하는 방식이 더 많이 사용된다.
package com.thread; public class ThreadExample1 { public static void main(String[] args) { // Thread thread = new Thread(/* Runnable 인터페이스 */); // Thread thread = new Thread(new Task()); Thread thread = new Thread(new Runnable() { @Override // => 익명 객체 형태를 더 많이 사용한다. public void run() { // 작업 스레드가 처리할 코드 for (int i = 1000; i > 0; i--) { System.out.println("스레드 " + i); } } }); // 해당 스레드를 시작시킨다. thread.start(); thread.start(); thread.start(); thread.start(); thread.start(); for (int i = 0; i < 1000; i++) { System.out.println("메인 " + i); } } }
Java
복사
package com.thread; public class ThreadExample1 { public static void main(String[] args) { // Thread thread = new Thread(/* Runnable 인터페이스 */); // Thread thread = new Thread(new Task()); Thread thread = new Thread(new Runnable() { @Override // => 익명 객체 형태를 더 많이 사용한다. public void run() { // 작업 스레드가 처리할 코드 for (int i = 5; i > 0; i--) { System.out.println("스레드 " + i); try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } } } }); // 해당 스레드를 시작시킨다. thread.start(); // 메인 스레드 for (int i = 0; i < 5; i++) { System.out.println("메인 " + i); try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } } } }
Java
복사

멀티 스레드 - Thread 자식 클래스로 스레드 생성하기

작업 스레드 객체를 생성하는 또 다른 방법은 Thread의 자식 객체로 만드는 것이다.
Thread 클래스를 상속한 다음 run( ) 메소드를 재정의해서 스레드가 실행할 코드를 작성하고 객체를 생성하면 된다.
package com.thread; public class ThreadExample2 { public static void main(String[] args) { // Thread thread = new Thread의 자식 클래스(); Thread thread = new WorkerThread(); // 해당 스레드를 시작시킨다. thread.start(); // 메인 스레드 for (int i = 0; i < 5; i++) { System.out.println("메인 " + i); try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } } } } // ================ 메인 0 스레드 5 스레드 4 메인 1 스레드 3 메인 2 스레드 2 메인 3 스레드 1 메인 4
Java
복사

멀티 스레드 - 스레드 상태

스레드 상태는 Thread.State 타입으로 정의되어 있다. (NEW, RUNNABLE, TERMINATED, TIMED_WAITING, BLOCKED, WAITING)
RUNNABLE - 실행과 실행 대기 반복, CPU를 다른 스레드들과 나눠 사용
RUNNABLE 상태에서는 상황에 따라 TIMED_WAITING, BLOCKED, WAITING 상태로 전환될 수 있다.
package com.thread2; public class ThreadStateExample { public static void main(String[] args) { // 스레드의 상태 // NEW : 스레드가 생성된 후, start 전 // RUNNABLE : start 후 실행 준비가 된 상태 (실행 중일 수도.. 실행 대기일 수도..) // TERMINATED : 실행 종료 Thread.State state; Thread thread = new Thread() { @Override public void run() { for (int i = 0; i < 1000000000; i++) { } } }; state = thread.getState(); // enum 타입이어서 사용 가능 System.out.println("스레드 상태1: " + state); // 스레드 상태1: NEW thread.start(); state = thread.getState(); System.out.println("스레드 상태2: " + state); // 스레드 상태2: RUNNABLE try { // CPU를 해당 10억번 작업 스레드가 모두 사용하도록 메인 스레드 대기 thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } state = thread.getState(); System.out.println("스레드 상태3: " + state); // 스레드 상태3: TERMINATED } }
Java
복사

멀티 스레드 - 스레드 일시정지

예시 - 다운로드 한 폴더를 여는 과정
package com.thread3; public class ThreadA extends Thread { @Override public void run() { ThreadB threadB = new ThreadB(); threadB.start(); try { // 스레드 일임을 한다. threadB.join(); } catch (InterruptedException e) { e.printStackTrace(); } Thread.currentThread().setName("폴더 열기"); String name = Thread.currentThread().getName(); System.out.println(name + " 시작"); System.out.println("폴더를 엽니다."); // 다운로드 완료되면 System.out.println(name + " 끝"); } }
Java
복사
package com.thread3; public class ThreadB extends Thread { @Override public void run() { Thread.currentThread().setName("다운로드"); String name = Thread.currentThread().getName(); System.out.println(name + " 시작"); for (int i = 0; i <= 100; i += 10) { System.out.println("다운로드: " + i + "%"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(name + " 끝"); } }
Java
복사
package com.thread3; public class ThreadPauseExample { public static void main(String[] args) { Thread thread = new Thread(new ThreadA()); thread.start(); } }
Java
복사

멀티 스레드 - 스레드 양보

스레드가 처리하는 작업은 반복처리가 많은 편인데, 가끔은 반복이 무의미한 반복으로 처리되는 경우가 있다.
이 때는 다른 스레드에게 실행을 잠시 양보하고, 자신은 실행 대기 상태로 가는 것이 프로그램 성능에 도움을 준다.
Thread는 yield( ) 메소드를 제공하여, yield( )를 호출한 스레드는 실행 대시 상태로 돌아가고, 다른 스레드가 실행 상태가 된다.
package com.thread4; public class ThreadLoop extends Thread { public boolean work = true; @Override public void run() { while (true) { if (work) { System.out.println(Thread.currentThread().getName() + "의 작업처리"); } else { Thread.yield(); } } } }
Java
복사
package com.thread4; public class ThreadLoopExample { public static void main(String[] args) { ThreadLoop thread0 = new ThreadLoop(); ThreadLoop thread1 = new ThreadLoop(); thread0.setName("첫번째 스레드"); thread1.setName("두번째 스레드"); thread0.start(); thread1.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } thread0.work = false; try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } thread0.work = true; } }
Java
복사

멀티 스레드 추가 내용

스레드 동기화
멀티 스레드는 하나의 객체를 공유해서 작업도 할 수 있다. 하지만 다른 스레드에서 객체를 조작한다면, 의도했던 결과와는 다른 결과를 초래할 수 있다. 스레드가 사용 중인 객체를 다른 스레드가 변경할 수 없도록 객체에 잠금을 걸 수 있다.
동기화 메소드와 동기화 블록은 동시 접근이 불가능하다.
wait(), notify()
두 개의 스레드를 가지고 정확히 교대로 번갈아가며 작업하도록 처리할 수 있다.
한 스레드가 작업을 완료하면 notify( ) 메소드를 호출해, 일시정지에 있는 다른 스레드를 실행대기 상태로 만들고, 자신은 두 번 작업을 하지 않도록 wait( ) 메소드를 호출하여 일시정지 상태로 만든다.
주의할 점은 wait(), notify() 메소드 모두 동기화 메소드 또는 동기화 블록 내에서만 사용 가능하다는 것이다.
스레드 안전 종료
조건 분기 처리 또는 interrupt( ) 메소드 사용으로 후, 리소스 정리 작업을 추가하는 것으로 스레드를 안전하게 종료할 수 있다.
데몬 스레드
데몬 스레드는 주 스레드의 작업을 돕는 보조적인 스레드로, 주 스레드가 종료되면 자동으로 종료된다.
데몬 스레드로 만들기 위해서는 start( ) 호출 전에 setDaemon(true)를 호출하면 된다

자료구조

선형 자료구조
데이터가 순차적으로 배열되는 자료구조
배열, ArrayList, LinkedList, Stack, Queue 등등…
List는 연속방식연결방식으로 나뉜다.
연속 방식 (메모리 공간 기반) ⇒ 한 번 정해진 길이를 변경할 수 없다!
연결 방식 (포인터_메모리주소번지 기반) - LinkedList
동적 배열의 원리 - 더블링 (시간복잡도 1에 수렴)
미리 초기값을 잡아 배열의 길이를 정해서 생성하고 데이터가 추가되면 늘려주고, 모두 복사해서 만들어준다.
더블링은 진행될 때마다 O(n)이다. 하지만 자주 일어나는 것이 아니므로 O(1)로 간주하고 프로그래밍을 하는 것이 일반적이다.

컬렉션 자료구조

자료구조를 바탕으로 객체들을 효율적으로 이용할 수 있도록 관련된 인터페이스와 클래스들을 java.util 패키지에 포함시켜 놓았다.
이들을 총칭해서 컬렉션 프레임워크(Collection Framework)라고 부른다.
컬렉션 프레임워크는 몇 가지 인터페이스를 통해서 다양한 컬렉션 클래스를 이용할 수 있도록 설계되어 있다.
주요 인터페이스로는 List, Set, Map 등이 있다.

컬렉션 자료구조

컬렉션 자료구조 - List 컬렉션

객체를 인덱스로 관리하기에 객체를 저장하면 인덱스가 부여되고, 인덱스로 객체를 검색, 삭제할 수 있는 기능을 제공한다.
ArrayList
List 컬렉션에서 가장 많이 사용되는 컬렉션이다.
ArrayList는 제한 없이 객체를 추가할 수 있다.
List<E> list = new ArrayList<>() ; // E에 지정된 타입의 객체만 저장 List<E> list = new ArrayList<E>(); // E에 지정된 타입의 객체만 저장 List list = new ArrayList(); // 모든 타입의 객체를 저장
Java
복사

Vector

Vector는 ArrayList와 동일한 내부 구조를 가지고 있으며, 동기화된 메소드로 구성되어 있어서 멀티 스레드가 동시에 Vector 메소드를 실행할 수 없다는 점에서 차이가 있다.
멀티 스레드 환경에서도 안전하게 객체를 추가, 삭제할 수 있다.
List<E> list = new Vector<E>(); // E에 지정된 타입의 객체만 저장 List<E> list = new Vector<E>(); // E에 지정된 타입의 객체만 저장 List list = new Vector(); // 모든 타입의 객체를 저장
Java
복사

컬렉션 자료구조 - List 컬렉션(LinkedList)

LinkedList는 ArrayList와 사용 방법은 동일하지만 내부 구조는 완전히 다르다.
ArrayList는 내부 배열에 객체를 저장하지만, LinkedList는 인접 객체를 사슬 형태로 연결해서 관리한다.
List<E> list = new LinkedList<E>(); // E에 지정된 타입의 객체만 저장 List<E> list = new LinkedList<E>(); // E에 지정된 타입의 객체만 저장 List list = new LinkedList(); // 모든 타입의 객체를 저장
Java
복사
비교
package com.collection.list; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; public class LinkedListExample { public static void main(String[] args) { // ArrayList 객체 생성 [문자열] List<String> list1 = new ArrayList<>(); // LinkedList 객체 생성 [문자열] List<String> list2 = new LinkedList<>(); // 시간 측정을 위한 변수 선언 long startTime; long endTime; startTime = System.nanoTime(); for (int i = 0; i < 10000; i++) { list1.add(0, String.valueOf(i)); } endTime = System.nanoTime(); System.out.println("추가 시 걸린 시간 (ArrayList): " + (endTime - startTime)); startTime = System.nanoTime(); for (int i = 0; i < 10000; i++) { list2.add(0, String.valueOf(i)); } endTime = System.nanoTime(); System.out.println("추가 시 걸린 시간 (LinkedList): " + (endTime - startTime)); startTime = System.nanoTime(); for (int i = 0; i < 10000; i++) { list1.get(i); } endTime = System.nanoTime(); System.out.println("조회 시 걸린 시간 (ArrayList) : " + (endTime - startTime)); startTime = System.nanoTime(); for (int i = 0; i < 10000; i++) { list2.get(i); } endTime = System.nanoTime(); System.out.println("조회 시 걸린 시간 (LinkedList) : " + (endTime - startTime)); } } // ========================================== 추가 시 걸린 시간 (ArrayList): 5314333 추가 시 걸린 시간 (LinkedList): 961125 조회 시 걸린 시간 (ArrayList) : 296125 조회 시 걸린 시간 (LinkedList) : 47712083
Java
복사

컬렉션 자료구조 - Set 컬렉션

List 컬렉션은 저장 순서를 유지하지만, Set 컬렉션은 저장 순서가 유지되지 않는다. 또한 객체를 중복해서 저장할 수 없고, 하나의 null만 저장할 수 있다.
Set 컬렉션은 수학의 집합에 비유될 수 있다.
Set 컬렉션은 순서가 없기 때문에 인덱스로 관리하지 않는다. 즉, 인덱스를 매개값으로 갖는 메소드가 없다.
Set 컬렉션에는 HashSet, LinkedHashSet, TreeSet 등이 있는데, Set 컬렉션에서 공통적으로 사용 가능한 Set 인터페이스 메소드는 아래와 같다.

HashSet

Set 컬렉션 중에서 가장 많이 사용되는 것이 HashSet이다.
HashSet은 hashCode( ) 메소드의 리턴값이 같고, equals( ) 메소드가 true를 반환하면 동일한 객체로 판단하고 중복 저장하지 않는다.
package com.collection.list; public class Member { private String name; private int age; public Member(String name, int age) { this.name = name; this.age = age; } @Override public int hashCode() { return name.hashCode() + age; } @Override public boolean equals(Object obj) { if (obj instanceof Member) { Member m = (Member) obj; return m.name.equals(name) && m.age == age; } return false; } } // ========================================== package com.collection.list; import java.util.HashSet; public class SetExample2 { public static void main(String[] args) { // HashSet 컬렉션 객체 생성 HashSet<Member> hashSet = new HashSet<>(); // 동일한 데이터 입력 hashSet.add(new Member("minsung", 26)); hashSet.add(new Member("minsung", 26)); // 저장된 객체 수를 출력 System.out.println("저장된 객체 수 : " + hashSet.size()); } } // 저장된 객체 수 : 1
Java
복사
package com.collection.list; import java.util.HashSet; import java.util.Set; public class SetExample { public static void main(String[] args) { // HashSet 컬렉션 객체 생성 Set<String> set = new HashSet<>(); // 컬렉션 객체에 데이터 저장 - 순서 X set.add("Java"); set.add("JDBC"); set.add("JSP"); set.add("JPA"); set.add("Servlet"); set.add("MyBatis"); set.add("Java"); System.out.println("저장된 요소의 개수 : " + set.size()); System.out.println(set); } }
Java
복사

TreeSet

이진 트리(Binary Tree)를 기반으로 검색 기능을 강화한 Set 컬렉션이다.
이진 트리는 여러 개의 노드(node)가 트리 형태로 연결된 구조로, 루트 노드라고 불리는 하나의 노드에서 시작해 각 노드에 최대 2개의 노드를 연결할 수 있는 구조를 가지고 있다.
TreeSet에 저장되는 객체는 저장과 동시에 오름차순으로 정렬된다. (낮은 것은 왼쪽 자식 노드에, 높은 것은 오른쪽 자식 노드에 저장)
어떤 객체든지 오름차순으로 정렬될 수 있는 것은 아니고, Comparable 인터페이스를 구현하고 있는 객체만이 정렬 가능하다.
Set 타입 변수에 대입해도 되지만, TreeSet 타입으로 대입한 이유는 검색 관련 메소드가 TreeSet에만 정의되어 있기 때문이다.
TreeSet<E> treeSet = new TreeSet<E>(); TreeSet<E> treeSet = new TreeSet<>();
Java
복사

Map

Map 컬렉션은 키(Key)와 값(Value)으로 구성된 엔트리(Entry) 객체를 저장한다.
키와 값은 모두 객체이고, 키는 중복 저장할 수 없지만 값은 중복 저장할 수 있다는 특징이 있다.
만약 동일한 키로 값을 저장하면, 기존의 값이 새로운 값으로 대치된다.

컬렉션 자료구조 - Map 컬렉션(HashMap)

HashMap 은 키로 사용할 객체가 hashCode() 메소드의 리턴값이 같고, equals( ) 메소드가 true를 반환하는 경우, 동일 키로 간주하고 중복 저장을 허용하지 않는다.
package com.collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class MapExample1 { public static void main(String[] args) { // 이름과 나이를 가지고, HashMap 컬렉션 생성 Map<String, Integer> map = new HashMap<>(); map.put("김연아", 33); map.put("지민", 8); map.put("전지현", 42); map.put("박보검", 30); map.put("손흥민", 32); map.put("지민", 28); // 덮어쓴다. // 요소의 총 개수 System.out.println("총 entry 수 : " + map.size()); System.out.println(map); // 키를 통해 값 얻기 Integer sonAge = map.get("손흥민"); System.out.println("손흥민의 나이 : " + sonAge); // 키로 구성된 set를 구해서, 반복문을 통해 값만 구하기 Set<String> keySet = map.keySet(); Iterator<String> iterator = keySet.iterator(); while (iterator.hasNext()) { String key = iterator.next(); System.out.println(key); } } }
Java
복사