2020-10-19 TIL
7 minute read
요구사항 분석
Use-case (what)
- 사용 사례
- 액터가 시스템을 사용하여 달성하려는 업무 목표
- use-case identity (식별방법)
- 적정크기 (2주~6주) : RUP 개발 프로세스 <-> 에자일 개발 방법론(1시간~1일)
- 개발 단위 : 분석 -> 설계 -> 구현 -> 테스트
- (개발할) 시스템을 사용해서 처리하는 업무
- 한 사람이 한 번에 한 순간에 수행하는 업무
- 카운트가 가능(업무의 시작과 끝이 명확)한 단위로 업무를 쪼갠다.
- 특별한 예
- 업무가 아닌데도 use-case로 식별하는 경우
- 여러 use-case의 공통 시나리오 일 때
- 게시글 등록, 변경, 삭제 : 로그인 <- 업무는 아니다 : 다른 use-case 들의 관리를 쉽게하기 위해 따로 추출한다 -> 개발 편의성
- use-case 통합
- CRUD 게시글 관리ㄴ
- 게시글 등록 Create
- 게시글 조회 Read / Retreive
- 게시글 변경 Update
- 게시글 삭제 Delete
- 서로 관련된 업무인 경우 업무의 편의성을 위해 한개의 use-case로 합치기도 한다.
- XxxManage, XxxHandler, XxxService
- 단, 합친 use-case는 2주에서 6주 범위에서 개발 가능해야 한다.
- use-case diagram
- actor -> use-case -> secondary actor
- 비회원 -> 회원 가입하기(join member) -> 카카오 주소검색 서비스
- use-case diagram 2
- 비회원 -> 회원가입 —>(include) 주소검색(유스케이스로 뽑음) -> 카카오 주소검색
- 실행흐름
- 회원가입 - 주소검색 - 끝 (주소 검색을 하지 않고 끝으로 가는 방법은 없는 경우 : Include)
- 회원가입(주소 검색을 클릭) <— (extend : 선택사항) 주소검색 -> 카카오 주소검색
- 회원가입 - 끝
- 회원가입 주소검색 - 주소검색 - 끝
- use-case 예)
- 회원
- -> 프로젝트 관리
- -> 작업 관리
- -> 게시글 관리
- -> 회원 탈퇴
- 관리자
- -> 회원관리
- 회원가입 <- 비회원
- 로그인 <- 비회원
개발방법론 - RUP
- 래셔널 통합 프로세스
- Rational Unified Process
- 소프트웨어 개발 과정에서 누가, 언제, 어떻게, 무엇을 수행해야 하는가를 기술한다.
- 작업자(Workers) : ‘누가(who)’에 해당
- 액티비티(Activities) : ‘어떻게(how)’에 해당
- 산출물(Artifacts) : ‘무엇을(what)’에 해당
- 웍플로우(Workflows) : ‘언제(when)’에 해당
스레드풀
ExcutorService
- 1) Runnable을 상속하는 클래스를 만들어서 run() 메서드에서 별도의 스레드로 독립적으로 실행해야 하는 일을 정의한다.
- 2) execute()를 호출하면
- 3) ExecutorService가 스레드를 생성한다.
- 4) 스레드가 클래스를 실행한다.
public class Exam0110 {
public static void main(String[] args) {
// 스레드풀을 생성한다.
// - 최대 3개의 스레드를 생성한다.
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 스레드풀에 작업 수행을 요청한다.
// - 작업은 Runnable 구현체로 작성하여 넘겨준다.(람다 문법 사용)
// - 스레드풀은 스레드를 생성하여 작업을 수행시킨다.
// class X implements Runnable {
// @Override
// public void run() {
// System.out.printf("%s 스레드 실행", Thread.currentThread().getName());
// }
// }
// new Runnable() {
// @Override
// public void run() {
// System.out.printf("%s 스레드 실행", Thread.currentThread().getName());
// }
// };
// Runnable obj = new Runnable() {
// @Override
// public void run() {
// System.out.printf("%s 스레드 실행", Thread.currentThread().getName());
// }
// };
//
// threadPool.execute(obj);
threadPool.execute(() ->
System.out.printf("%s 스레드 실행", Thread.currentThread().getName()));
System.out.println("main() 종료");
System.out.println("main() 종료!");
// JVM은 main 스레드가 종료하더라도 나머지 스레드가 종료할 때까지 기다린다.
// 스레드풀에서 생성한 스레드가 요청한 작업을 마치더라도
// 다음 작업을 수행하기 위해 계속 실행된 채로 대기하고 있기 때문에
// JVM은 종료하지 않는다.
}
}
ExecutorExcutorService - 스레드풀 종료
public class Exam0120 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(() -> System.out.printf("%s - Hello!\n",
Thread.currentThread().getName()));
// 스레드풀에 있는 모든 스레드들이 요청한 작업을 끝내면 종료하게 한다.
// 모든 스레드가 종료될 때까지 기다리지 않고 바로 리턴한다.
// shutdown() 호출 이후에는 새 작업 요청을 받지 않는다.
// 즉 execute()를 호출하면 예외가 발생한다.
executorService.shutdown();
System.out.println("main() 종료!");
}
}
고정 크기 스레드풀 - newFixedThreadPool(숫자)
public class Exam0210 {
static class MyRunnable implements Runnable {
int millisec;
public MyRunnable(int millisec) {
this.millisec = millisec;
}
@Override
public void run() {
try {
System.out.printf("[%s] 스레드 실행 중...\n",
Thread.currentThread().getName());
Thread.sleep(millisec);
System.out.printf("[%s] 스레드 종료!\n",
Thread.currentThread().getName());
} catch (Exception e) {
System.out.printf("[%s] 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 일단 스레드풀의 크기(3개)만큼 작업 수행을 요청한다.
// - 작업은 큐에 등록된 순서대로 보관된다.
// - 스레드풀은 큐에서 작업을 꺼내 스레드에게 일을 시킨다.
//
executorService.execute(new MyRunnable(6000));
executorService.execute(new MyRunnable(3000));
executorService.execute(new MyRunnable(9000));
// 스레드풀의 크기를 초과해서 작업 수행을 요청한다면?
// - 놀고 있는 스레드가 없을 경우,
// 다른 스레드의 작업이 끝날 때까지 작업큐에 대기하고 있는다.
// - 작업을 끝낸 스레드가 생기면 큐에서 작업을 꺼내 실행한다.
//
executorService.execute(new MyRunnable(2000));
executorService.execute(new MyRunnable(4000));
System.out.println("main() 종료!");
}
}
가변 크기 스레드풀 - newCachedThreadPool()
- 문제점 : 한번에 백만명이 접속하면 스레드가 한번에 백만개가 생성된다.
public class Exam0220 {
.
.
.
public static void main(String[] args) throws Exception {
// 스레드의 수를 고정하지 않고 필요할 때마다 스레드를 생성하는 스레드풀이다.
// 물론 작업을 끝낸 스레드는 다시 사용할 수 있도록 pool에 보관한다.
ExecutorService executorService = Executors.newCachedThreadPool();
// 놀고 있는 스레드가 없으면 새 스레드를 생성한다.
//
executorService.execute(new MyRunnable(6000));
executorService.execute(new MyRunnable(3000));
executorService.execute(new MyRunnable(9000));
executorService.execute(new MyRunnable(2000));
// 작업을 끝낸 스레드가 생길 때까지 일부러 기다린다.
//
Thread.sleep(3000);
// 그러면 새 스레드를 생성하지 않고
// 작업을 끝낸 스레드가 요청한 작업을 처리한다.
//
executorService.execute(new MyRunnable(4000));
System.out.println("main() 종료!");
}
}
한 개의 스레드를 갖는 스레드풀 - newSingleThreadExecutor()
public class Exam0230 {
.
.
.
public static void main(String[] args) throws Exception {
// 한 개의 스레드만 갖는 스레드풀이다.
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 스레드가 한 개이기 때문에 순차적으로 실행한다.
executorService.execute(new MyRunnable(6000));
executorService.execute(new MyRunnable(3000));
executorService.execute(new MyRunnable(9000));
executorService.execute(new MyRunnable(2000));
executorService.execute(new MyRunnable(4000));
System.out.println("main() 종료!");
}
}
작업 실행 : execute() 와 submit()
- execute()
- 리턴 타입이 void
- 스레드풀에 수행할 작업을 등록한다.
- 스레드풀은 execute()를 호출한 순서대로 작업큐에 작업을 보관한다.
- 그리고 놀고 있는 스레드가 있다면, 작업큐에서 작업을 꺼 수행시킨다.
- 놀고 있는 스레드가 없으면, 새로 스레드를 생성한다.
- 놀고 있는 스레드가 없으면, 새로 스레드를 생성한다.
- submit()
- execute()와 같다.
- 단 작업의 종료 상태를 확인할 수 있는 Future 객체를 리턴한다.
- 특정 작업이 끝날 때까지 기다리고 싶을 때 future를 쓴다.
public class Exam0320 {
.
.
.
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(3);
Future<?> future1 = executorService.submit(new MyRunnable(2000));
Future<?> future2 = executorService.submit(new MyRunnable(4000));
// Future.get()
// => 요청한 작업이 완료될 때 까지 기다린다.(pending)
// => 요청한 작업이 완료되면 null을 리턴한다.
// if 문은 해당 작업이 끝나면 null을 리턴하는 것을 확인시키기 위해 사용하는 것이다.
if (future2.get() == null)
System.out.println("두 번째 작업이 끝났음");
if (future1.get() == null)
System.out.println("첫 번째 작업이 끝났음");
// future2.get(); // 해당 작업이 끝날 때까지 기다리다가, 끝나 null을 리턴한다.
// System.out.println("두 번째 작업이 끝났음");
//
// future1.get(); // // 해당 작업이 끝날 때까지 기다리다가, 끝나 null을 리턴한다.
// System.out.println("첫 번째 작업이 끝났음");
System.out.println("main() 종료!");
}
}
스레드풀 종료 - shutdown()
- shutdown을 호출하면 이전에 요청한 작업들이 완료될 때 까지 기다렸다가 스레드풀이 종료된다.
- shutdown을 호출한 다음에 작업을 요청하면 예외가 발생된다.
public class Exam0410 {
.
.
.
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(new MyRunnable(6000));
executorService.execute(new MyRunnable(2000));
executorService.execute(new MyRunnable(4000));
executorService.execute(new MyRunnable(4000));
executorService.execute(new MyRunnable(4000));
executorService.execute(new MyRunnable(4000));
// 더이상 작업 요청을 받지 말고
// 이전에 요청한 작업들이 완료되면
// 스레드를 종료하도록 예약한다.
executorService.shutdown();
// 작업 요청을 거절한다.
// => 예외 발생!
executorService.execute(new MyRunnable(4000));
// 위에서 예외가 발생했기 때문에 "main() 종료" 는 출력되지 않는다.
System.out.println("main() 종료!");
}
}
스레드풀 즉시 종료 - shutdownNow()
- 실행중인 작업은 즉시 멈추고 대기중인 작업은 리턴한다.
public class Exam0420 {
.
.
.
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(new MyRunnable(6000));
executorService.execute(new MyRunnable(2000));
executorService.execute(new MyRunnable(4000));
executorService.execute(new MyRunnable(5000));
executorService.execute(new MyRunnable(6000));
executorService.execute(new MyRunnable(7000));
// 현재 수행 중인 작업들을 모두 멈추도록 지시한다.
// => 대기 중인 작업들은 취소한다.
// => 그리고 취소한 작업 목록을 리턴해준다.
List<Runnable> tasks = executorService.shutdownNow();
for (Runnable task : tasks) {
System.out.println(((MyRunnable) task).millisec);
}
// 물론 새 작업 요청도 거절한다.
// => 예외 발생!
executorService.execute(new MyRunnable(4000));
System.out.println("main() 종료!");
}
}
shutdown() vs shutdownNow()
- shutdown() : 진행 중인 작업을 완료하고 대기 중인 작업도 완료한 다음 종료.
- shutdownNow() : 진행 중인 작업은 즉시 완료하고 대기 중인 작업은 리턴한다.
스레드풀 종료 대기 - awaitTermination()
- 지정한 시간이 경과될 때까지 종료되지 않고 기다렸다가 지정된 시간이 지나면 더 이상 기다리지 않고 메인을 종료하고 종료되지 않은 스레드가 마감될 때 까지 기다린다.
public class Exam0510 {
.
.
.
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(new MyRunnable(6000));
executorService.execute(new MyRunnable(2000));
executorService.execute(new MyRunnable(4000));
executorService.execute(new MyRunnable(3000));
executorService.shutdown();
// 스레드풀의 모든 스레드가 종료되면 즉시 true를 리턴한다.
// 만약 지정된 시간이 경과할 때 까지 종료되지 않았다면
// false를 리턴한다.
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
System.out.println("아직 종료 안된 작업이 있다.");
} else {
System.out.println("모든 작업을 종료하였다.");
}
System.out.println("main() 종료!");
}
}
awaitTermination()과 shutdownNow()를 함께 사용했을 때
public class Exam0520 {
.
.
.
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(new MyRunnable(6000));
executorService.execute(new MyRunnable(2000));
executorService.execute(new MyRunnable(4000));
executorService.execute(new MyRunnable(20000));
executorService.shutdown();
// 스레드풀의 모든 스레드가 종료될 때까지 기다린다.
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
System.out.println("아직 종료 안된 작업이 있다.");
System.out.println("남아 있는 작업의 강제 종료를 시도하겠다.");
// => 만약 10초가 경과될 때까지 종료되지 않으면,
// 수행 중인 작업을 강제 종료하라고 지시하고,
// 대기 중인 작업은 취소한다.
executorService.shutdownNow();
// 그리고 다시 작업이 종료될 때까지 기다린다.
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("스레드풀의 강제 종료를 완료하지 못했다.");
} else {
System.out.println("모든 작업을 강제 종료했다.");
}
}
System.out.println("main() 종료!");
}
}