일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- islowercase()
- 동일성과 동등성
- addDoc
- 자바 유클리드
- 모던자바
- 스프링
- isuppercase()
- 최소공배수
- 자바 최대공약수
- 최대공약수와 최소공배수
- 베주계수
- Git사용법
- 래퍼타입
- 스프링뼈대
- stringbuilder의 reverse()
- ineer join
- git 컨벤션
- replaceAll()
- 스프링환경설정
- 자바 최소공배수
- 최대공약수
- 유클리드호제법
- 프로그래머스 레벨1
- while과 two-pointer
- StringBuilder
- sql 데이터형 변환
- toLowerCase()
- string과 stringbuilder
- 자바 스트링
- string
- Today
- Total
주노 님의 블로그
20240725 본캠프 9일차 TIL 본문
본캠프 9일차 내용 간단요약
- 09:00 ~ 10:30 : 코드카타
- 10:30 ~ 12:00 : 강의
자바 문법 종합반 5주차 1강 ~ 5강 - 12:00 ~ 13:00 : 점심시간
- 13:00 ~ 18:00 : 강의
자바 문법 종합반 5주차 6강 ~ 13
5주차 과제 - 18:00 ~ 19:00 : 저녁시간
- 19:00 ~ 20:00 : TIL작성
- 20:00 ~ 21:00 : 개인과제
Lv1 풀기
오늘 해야할 일✔️ 🔺 ❌
✔️ 5주차 강의듣기
✔️ 개인과제 레벨1
코드카타 - 알고리즘
네오와 프로도가 숫자놀이를 하고 있습니다. 네오가 프로도에게 숫자를 건넬 때 일부 자릿수를 영단어로 바꾼 카드를 건네주면 프로도는 원래 숫자를 찾는 게임입니다.
다음은 숫자의 일부 자릿수를 영단어로 바꾸는 예시입니다.
1478 → "one4seveneight"
234567 → "23four5six7"
10203 → "1zerotwozero3"
이렇게 숫자의 일부 자릿수가 영단어로 바뀌어졌거나, 혹은 바뀌지 않고 그대로인 문자열 s가 매개변수로 주어집니다. s가 의미하는 원래 숫자를 return 하도록 solution 함수를 완성해주세요.
참고로 각 숫자에 대응되는 영단어는 다음 표와 같습니다.
숫자 영단어
0 zero
1 one
2 two
3 three
4 four
5 five
6 six
7 seven
8 eight
9 nine
제한사항
1 ≤ s의 길이 ≤ 50
s가 "zero" 또는 "0"으로 시작하는 경우는 주어지지 않습니다.
return 값이 1 이상 2,000,000,000 이하의 정수가 되는 올바른 입력만 s로 주어집니다.
입출력 예
s result
"one4seveneight" 1478
"23four5six7" 234567
"2three45sixseven" 234567
"123" 123
위 문제는 간단하게 replaceAll을 사용하면된다
class Solution {
public int solution(String s) {
int answer = 0;
s = s.replaceAll("zero", "0");
s = s.replaceAll("one", "1");
s = s.replaceAll("two", "2");
s = s.replaceAll("three", "3");
s = s.replaceAll("four", "4");
s = s.replaceAll("five", "5");
s = s.replaceAll("six", "6");
s = s.replaceAll("seven", "7");
s = s.replaceAll("eight", "8");
s = s.replaceAll("nine", "9");
answer = Integer.parseInt(s);
return answer;
}
}
StringBuilder에는 저렇게 치환하는 메서드는 제공되지않기때문!
자바문법종합반 - 4주차 과제
과제는 계산기 예외처리이다.
1.코드분석
//Main.java
public class Main {
public static void main(String[] args) {
boolean calculateEnded = false;
// 구현 2.
}
}
메인 클래스이다
계산기를 실행하는 로직이 들어갈것이다
public class Calculator {
private int firstNumber;
private int secondNumber;
private AbstractOperation operation;
public Calculator(AbstractOperation operation) {
this.operation = operation;
}
public Calculator() {
}
public void setOperation(AbstractOperation operation) {
this.operation = operation;
}
public void setFirstNumber(int firstNumber) {
this.firstNumber = firstNumber;
}
public void setSecondNumber(int secondNumber) {
this.secondNumber = secondNumber;
}
public double calculate() {
double answer = 0;
answer = operation.operate(this.firstNumber, this.secondNumber);
return answer;
}
}
연산자, 첫번째 두번째 값을 받는 메서드와
계산을 수행하는 calculate 메서드가 보인다
public class BadInputException extends Exception {
public BadInputException(String type) {
super("잘못된 입력입니다! " + type + "을 입력해주세요!");
}
}
badInputException이며, 입력값에 벗어나면 예외를 던지는 메서드이다
import java.util.regex.Pattern;
public class Parser {
private static final String OPERATION_REG = "[+\\-*/]";
private static final String NUMBER_REG = "^[0-9]*$";
private final Calculator calculator = new Calculator();
public Parser parseFirstNum(String firstInput) {
// 구현 1.
}
public Parser parseSecondNum(String secondInput) {
// 구현 1.
}
public Parser parseOperator(String operationInput) {
// 구현 1.
}
public double executeCalculator() {
return calculator.calculate();
}
}
우리가 구현할 String값들을 받는 메서드들인것같다
import java.util.Scanner;
public class CalculatorApp {
public static boolean start() throws Exception{
Parser parser = new Parser();
Scanner scanner = new Scanner(System.in);
System.out.println("첫번째 숫자를 입력해주세요!");
String firstInput = scanner.nextLine();
parser.parseFirstNum(firstInput);
System.out.println("연산자를 입력해주세요!");
String operator = scanner.nextLine();
parser.parseOperator(operator);
System.out.println("두번째 숫자를 입력해주세요!");
String secondInput = scanner.nextLine();
parser.parseSecondNum(secondInput);
System.out.println("연산 결과 : " + parser.executeCalculator());
return true;
}
}
계산기를 구동하는 메서드인것같다
익셉션을 던지는 것을 봐서는 예외처리를 해줘야할 것 같고
아마 저번주 과제에서 생각한 0으로 나눌때의 문제를 잡으려고 하는것 같다
각 숫자가 입력되면 parser에서 문자를 숫자로 바꿔야 할 것 같다
연산결과는 excutecalculator메서드를 호출하고
public double executeCalculator() {
return calculator.calculate();
}
excutecalculator는 calculate메서드를 호출한다
public double calculate() {
double answer = 0;
answer = operation.operate(this.firstNumber, this.secondNumber);
return answer;
}
보면 excutecalculater은 값을 반환만 하는것처럼 보이지만
parser에서 생성된 계산된 calculator객체를 옮겨주는 역할을 한다
2. 구현
public class Main {
public static void main(String[] args) {
boolean calculateEnded = false;
while (!calculateEnded)
{
CalculatorApp calculatorApp = new CalculatorApp();
try {
calculatorApp.start();
} catch (ArithmeticException e)
{
System.out.println("나눗셈일때 두번째 숫자는 0이 될 수 없습니다\n오류내용 : " + e.getMessage());
calculateEnded=true;
}catch (Exception e)
{
System.out.println(e.getMessage());
}
}
}
}
메인문은 바로 만들수 있었다
구체적인 오류를 정의하기 위해서 두번째 입력값이 0이 되는 값을 방지하기 위해
ArithmeticException을 달았다
그리고 CAlculatorApp에서 exception을 정의했기때문에 exception을 정의해준다.
그리고 에러가 뜨지않아서 고민을 했는데
e.getMessage는 sout으로 감싸줘야 출력되더라..
그리고 while문이 정지하려면
오류가 발생하였을때 정지하는게 좋은데
예외 처리란게 애초에 정지를 하지않기위해서 던지는게 아닌가?
라는 생각이 들었다.
package week4.work;
import java.util.regex.Pattern;
public class Parser {
private static final String OPERATION_REG = "[+\\-*/]";
private static final String NUMBER_REG = "^[0-9]*$";
private final Calculator calculator = new Calculator();
public Parser parseFirstNum(String firstInput) throws Exception {
if (!Pattern.matches(NUMBER_REG, firstInput)) {
throw new BadInputException(NUMBER_REG);
}
calculator.setFirstNumber(Integer.parseInt(firstInput));
return null;
}
public Parser parseSecondNum(String secondInput) throws BadInputException {
if (!Pattern.matches(NUMBER_REG, secondInput)) {
throw new BadInputException(NUMBER_REG);
}
calculator.setSecondNumber(Integer.parseInt(secondInput));
return null;
}
public Parser parseOperator(String operationInput) throws BadInputException {
if (!Pattern.matches(OPERATION_REG, operationInput)) {
throw new BadInputException(OPERATION_REG);
}
if (operationInput.equals("+")) {
calculator.setOperation(new AddOperation());
} else if (operationInput.equals("-")) {
calculator.setOperation(new SubstaractOperation());
} else if (operationInput.equals("*")) {
calculator.setOperation(new MultiplyOperation());
} else if (operationInput.equals("/")) {
calculator.setOperation(new DivideOperation());
}
return null;
}
public double executeCalculator() {
return calculator.calculate();
}
}
그리고 parser를 구현하였다
그리고 파싱 메서드에서 return값이 굳이 필요한가 싶었다. setSecondNumber이라는 메서드에서 직접 수정하고 있는데 말이다 그래서 return null로 두었다.
parse메서드가 실행될때 분기문으로 예외처리를 하는게 좋다고 생각됐다.
은근 구글링을 하지않아도 커서를 올리니 바로 알려주었다
저번주에 배운 내용을 생각해보자
단일 책임 원칙을 지켰는가? 녜.
parser은 문자열을 검증하고 파싱하는 책임을 담당하고
Calulator은 계산을,
Calulatorapp은 문자 입출력만을 담당하고 있다.
의존성 역전 원칙을 지켰는가? 녜
저수준 모듈과 고수준 모듈은 모두 추상화에 의존하고 있으며
추상화된 클래스는 세부사항이 없이 저수준모듈이 구현하게 설계되었다
쓰레드
- 프로세스 : 운영체제로부터 자원을 할당 받는 작업의 단위
- 쓰레드 : 프로세스가 할당받은 자원을 이용하는 실행의 단위
하나의 프로세스를 한개의 쓰레드가 작업을 한다 : 싱글 쓰레드
하나의 프로세스를 여러개의 쓰레드가 작업을 한다 : 멀티 쓰레드 - 운영체제가 프로그램 실행을 위한 프로세스를 할당해줄때 프로세스 안에 프로그램 코드와 데이터 그리고 메모리영역을 할당해준다
1. 코드 : 자바 같은 코드
2. 데이터 : 프로그램이 실행중 저장 할 수 있는 저장공간 (전역변수, static변수, 배열등)
3. 메모리영역
stack : 지역변수 매개변수등 리턴 변수를 저장
heap : 동적으로 필요한 변수를 저장하는 공간 (객체) - 프로세스가 작업중인 프로그램에서 실행 요청이 들어오면, 쓰레드가 명령을 처리하도록 한다
쓰레드는 메모리공간을 공유를 받아 작업을 수행한다
자바에서는 메모리공간은 jvm내의 영역이다 - 싱글쓰레드
프로세스 안에서 하나의 쓰레드만 실행되는것
Java 프로그램은 main안에서 실행시켰기때문에 싱글쓰레드이다. - 멀티 쓰레드
프로세스가 여러개의 쓰레드를 가지고 있다.
Java프로그램은 메인 쓰레드외 다른 쓰레드를 생성해서 여러개의 실행흐름을 만들수있다
장점
쓰레드가 여러개 이기 때문에 여러개의 작업을 동시에 할 수 있어서 성능이 좋아진다
자원을 보다 효율적으로 사용할 수 있다
단점
자원을 공유하면서 동기화 문제가 생길 수 있다
데드락(둘 이상의 쓰레드가 서로의 자원을 원할때 서로의 작업이 종료되기만을 기다려 작업을 진행하지 못하는 상태) 가 일어날 수 있다. - 쓰레드 구현
public class W01Main { //psvm으로 메인 쓰레드를 동작시킴 public static void main(String[] args) { W01 thread = new W01(); thread.start(); } } package week5; //1. 쓰레드 클래스를 이용하는것 public class W01 extends Thread{ @Override public void run() { // 실제 우리가 쓰레드에서 수행할 작업 for (int i = 0; i < 100; i++) { System.out.print("*"); } } }
Thread를 상속받으면
run메서드가 있다. run으로 쓰레드의 작업을 정해주고,
start메서드로 실행한다
Runnable task = () -> { int sum = 0; for (int i = 0; i < 50; i++) { sum += i; System.out.println(sum); } System.out.println(Thread.currentThread().getName() + " 최종 합 : " + sum); }; Thread thread1 = new Thread(task); thread1.setName("thread1");//쓰레드 이름을 정함 Thread thread2 = new Thread(task); thread2.setName("thread2"); //메인 쓰레드 안에서 두개의 추가의 쓰레드가 있음 thread1.start(); thread2.start();
람다식으로 구현한 메서드이며
task를 수행한다.. 정도로만 생각하면 된다
두개의 쓰레드가 실행된다.
위 코드를 실행하면 1~50까지 증가하며 더하는 task인데
출력내용을 보면 0 1 3 6 10 15 0 1 3 6 21을 볼 수 있다.
이것은 쓰레드1과 쓰레드2가 병렬적으로 출력하고 있어서 현재처럼 보이는것이다
원래라면 싱글쓰레드라서 첫번째를 1225까지 출력하고, 두번째를 1225까지 출력하겠지만
멀티쓰레드 환경에서는 병렬적으로 처리한다.
정해진 패턴을 번갈아가면서 수행하는것이 아니다 >> 출력이 계속 변하는것을 확인 할 수 있다.
그래서 걸리는 시간이나 동작을 예상할수 없다 - 데몬쓰레드
사용자가 생성한 쓰레드가아닌 낮은 우선순위를 가진 쓰레드이다.
가비지 컬렉터가 있다.
setDemon(true) 데몬 스레드로 할당한다
Runnable demon = () -> { for (int i = 0; i < 1000000; i++) { System.out.println(i + "번째 데몬"); } }; //데몬 스레드는 우선순위가 낮다! 상대적으로 다른 쓰레드에 비해 리소스를 적게 할당받는다 Thread thread = new Thread(demon); thread.setDaemon(true); // true로 설정시 데몬스레드로 실행됨 thread.start(); //메인스레드는 진행되다가 끝나면 데몬쓰레드를 기다리지 않고 끝나버린다 for (int i = 0; i < 100; i++) { System.out.println("task"); }
데몬쓰레드와 메인쓰레드가 있는데
우선순위가 높은 메인쓰레드가끝나면 데몬쓰레드도 끝나버린다
즉, jvm은 사용자의 쓰레드 작업이 끝나면 데몬쓰레드도 자동으로 종료된다.
쓰레드의 우선순위를 지정 할 수 있다
1~10 사이의 숫자로 숫자가 높을수록 우선순위가 높다
public static void main(String[] args) { Runnable task1 = () -> { for (int i = 0; i < 100; i++) { System.out.print("$"); } }; Runnable task2 = () -> { for (int i = 0; i < 100; i++) { System.out.print("*"); } }; Thread thread1 = new Thread(task1); //setPriority로 우선순위를 지정 할 수 있다 10에 가까울수록 우선순위 높음 thread1.setPriority(8); int threadPriority = thread1.getPriority(); System.out.println("threadPriority = " + threadPriority); Thread thread2 = new Thread(task2); //1에 가까울수록 우선순위가 높다 thread2.setPriority(2); thread1.start(); thread2.start(); }
상대적으로 $는 일찍 끝났다.
- 쓰레드 그룹
모든 쓰레드는 반드시 하나의 그룹에 속해있어야한다
JVM이 시작되면 system그룹이 생성되고, 기본적으로 system그룹을 생성한다.
메인쓰레드는 system그룹 하위에 main그룹에 포함된다.
public static void main(String[] args) { Runnable task = () -> { //쓰레드가 인터럽트를 받지 않는동안 1초동안 쓰레드이름을 찍는다. while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()); } catch (InterruptedException e) { break; } } System.out.println(Thread.currentThread().getName() + " Interrupted"); }; // ThreadGroup 클래스로 객체를 만듭니다. ThreadGroup group1 = new ThreadGroup("Group1"); // Thread 객체 생성시 첫번째 매개변수로 넣어줍니다. // Thread(ThreadGroup group, Runnable target, String name) // 쓰레드 1과 2는 그룹 1에 포함된다. Thread thread1 = new Thread(group1, task, "Thread 1"); Thread thread2 = new Thread(group1, task, "Thread 2"); // Thread에 ThreadGroup 이 할당된것을 확인할 수 있습니다. //그룹이름 찍기 System.out.println("Group of thread1 : " + thread1.getThreadGroup().getName()); System.out.println("Group of thread2 : " + thread2.getThreadGroup().getName()); thread1.start(); thread2.start(); // 현재 쓰레드를 지정된 시간동안 멈추게 합니다. try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // interrupt()는 일시정지 상태인 쓰레드를 실행대기 상태로 만듭니다. //그룹 자체를 인터럽트 시켜버린다. group1.interrupt(); }
위 코드를 확인하면 쓰레드1과 쓰레드2를 새로운 그룹1로 할당한다
그리고 try catch문에서 메인쓰레드를 5초동안 정지하는 것을 볼 수 있다
맨위의 while문에서 현재 쓰레드가 인터럽트를 발생하지 않는다면.
1초마다 프린트를 찍게한다
1초마다 쓰레드는 번갈아가며 출력하다 5초가 지나면 메인쓰레드의 동작이 돌아가며
마지막의 그룹 1로 설정된 쓰레드를 정지시킨다.
쓰레드 그룹화의 중요성은 원하는 쓰레드를 일괄 처리해야할때 좋다. - 쓰레드 상태
실행, 실행대기중인 쓰레드는
sleep, join, wait메서드로 일시정지 상태가 될 수 있으며
일시정지 상태에서 interrupt, notify 메서드로 다시 실행대기상태로 갈 수 있다.
쓰레드는 new 키워드로 생성된다.
strat() 메서드를 호출하는 순간 쓰레드는 실행 대기 상태가 된다
os의 스케쥴러에 의해 run()메서드를 실행 > 실행대기를 반복한 후, 종료가된다
쓰레드의 상태
객체생성 NEW 쓰레드 객체 생성, 아직 start() 메서드 호출 전의 상태
실행대기 RUNNABLE 실행 상태로 언제든지 갈 수 있는 상태
일시정지 WAITING 다른 쓰레드가 통지(notify) 할 때까지 기다리는 상태
일시정지 TIMED_WAITING 주어진 시간 동안 기다리는 상태
일시정지 BLOCKED 사용하고자 하는 객체의 Lock이 풀릴 때까지 기다리는 상태
종료 TERMINATED 쓰레드의 작업이 종료된 상태 - sleep
현재 쓰레드를 지정된 시간동안만 멈출 수 있다.
public static void sleep( @Range(from = 0, to = Long. MAX_VALUE) long millis )
throws InterruptedException
sleep은 InterruptedException 을 던져주기때문에 try catch문으로 감싸줘야한다.
public static void main(String[] args) { Runnable task = () -> { try { //객체를 만든다음 객체.메서드로 호출하지만 //지금은 쓰레드 클래스를 호출하는것을 볼 수 있다. //sleep은 static이다. //특정 쓰레드가 아닌 try catch문에있는 task의 쓰레드가 멈추게된다. //쓰레드는 try catch문으로 예외처리를 해줘야한다. Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } //sleep때문에 2초를 멈추고 있다. System.out.println("task : " + Thread.currentThread().getName()); }; Thread thread = new Thread(task, "Thread"); //new thread.start(); // new > runnable try { //객체로 만든 쓰레드를 멈추게 하려고함 //static 멤버 'java.lang.Thread.sleep(long)'이(가) 인스턴스 참조를 통해 액세스됩니다 //라는 경고가 뜨게된다. thread.sleep(1000); System.out.println("sleep(1000) : " + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } }
sleep은 static이기때문에 클래스타입으로 직접 호출을 해줘야 한다.
그렇기떄문에 인스턴스를 통한 호출이든, 클래스를 통한 호출이든 현재 실행중인 쓰레드만 멈추게된다 - interrupt : 일시정지 상태인 쓰레드를 실행대기 상태로 만든다
task를 수행하는 쓰레드가 있다.public static void main(String[] args) { Runnable task = () -> { //인터럽트가 받지않은동안 while (!Thread.currentThread().isInterrupted()) { try { //1초동안 슬립 >> 슬립은 꼭 예외처리! Thread.sleep(1000); //1초 슬립후 이름출력하기 System.out.println(Thread.currentThread().getName()); } catch (InterruptedException e) { break; } } System.out.println("task : " + Thread.currentThread().getName()); }; //task를 수행하는 쓰레드를 만들고 Thread thread = new Thread(task, "Thread"); //new thread.start(); //new >runnable //start하는순간 task로. //메인쓰레드는 아래로 //쓰레드가 1초동안 멈춰야겠지만, 메인쓰레드가 인터럽트를 호출시켜서 에러가 뜨게된다. //따라서 sleep밑에있는 print는 출력되지 않는다! thread.interrupt(); System.out.println("thread.isInterrupted() = " + thread.isInterrupted()); }
메인 쓰레드가 쓰레드를 선언하고 시작하면 그 다른 쓰레드는 task를 순회하게 된다
하지만 메인쓰레드는 인터럽트를 바로 걸어버림으로써 1초동안 슬립해야하는 다른 쓰레드는 정지되게 되고, catch문으로 넘어가며, break가되어 while문을 벗어나게된다
즉 try문에 있는 print문은 실행되지 않는다. - join()
정해진 시간 동안 지정한 쓰레드가 작업하는것을 기다린다
시간을 지정하지 않았을때는 지정한 쓰레드의 작업이 끝날때까지 기다리게된다.
public static void main(String[] args) { Runnable task = () -> { try { //쓰레드는 5초간 대기한다 Thread.sleep(5000); // 5초 } catch (InterruptedException e) { e.printStackTrace(); } }; Thread thread = new Thread(task, "thread"); //new상태 thread.start();//new > runnable상태로 long start = System.currentTimeMillis(); // thread 의 소요시간인 5000ms 동안 main 쓰레드가 기다렸기 때문에 5000이상이 출력됩니다. System.out.println("소요시간 = " + (System.currentTimeMillis() - start)); }
위 코드에서는 메인쓰레드가 다른 쓰레드를 생성하긴하지만, 바로 sout으로 넘어가 출력하고 끝내버리기때문에 소요시간은 0초이다.
public static void main(String[] args) { Runnable task = () -> { try { //쓰레드는 5초간 대기한다 Thread.sleep(5000); // 5초 } catch (InterruptedException e) { e.printStackTrace(); } }; Thread thread = new Thread(task, "thread"); //new상태 thread.start();//new > runnable상태로 long start = System.currentTimeMillis(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } // thread 의 소요시간인 5000ms 동안 main 쓰레드가 기다렸기 때문에 5000이상이 출력됩니다. System.out.println("소요시간 = " + (System.currentTimeMillis() - start)); }
하지만 join(시간미지정)메서드를 추가하는순간 메인쓰레드는 다른쓰레드 작업을 기다린다. - yeild
양보하다!
다른쓰레드에 양보를 하고 실행대기가 된다.
public static void main(String[] args) { Runnable task = () -> { try { for (int i = 0; i < 10; i++) { //1초동안 PRINT를 총10개 출력한다. Thread.sleep(1000); System.out.println(Thread.currentThread().getName()); } } catch (InterruptedException e) { //쓰레드1은 인터럽트가 발생해 catch문으로 들어오게 된다 //쓰레드 2에게 모든 리소스를 건네주고 실행대기로 간다. Thread.yield(); } }; //태스크는 같은 멀티쓰레드 Thread thread1 = new Thread(task, "thread1"); Thread thread2 = new Thread(task, "thread2"); thread1.start(); thread2.start(); try { //메인쓰레드는 5초를 기다린다 Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } //쓰레드 1을 멈추게한다 thread1.interrupt(); }
쓰레드 1 2 선언 > 메인쓰레드는 5초를 기다린후 쓰레드1을 멈추게한다
쓰레드1은 catch문으로 들어가며, yield메서드로인해 쓰레드2에게 리소스를 넘겨주고 멈춘다. - Synchronized
멀티 쓰레드는 여러 쓰레드가 한 프로세스의 자원을 공유하기떄문에 동기화 문제등이 발생할 수 있다.
어떤 쓰레드가 작업을 할때 그 영역을 침범하지 못하도록 막는것을 sychronize라고 하며
그 영역을 임계영역이라고 한다.
임계영역에는 lock을 가진 쓰레드만이 출입할 수 있다.
동기화를 하지 않았을때는 사과의 수가 0초과에만 eatApple를 먹을수 있지만,public static void main(String[] args) { AppleStore appleStore = new AppleStore(); Runnable task = () -> { //사과의 수가 0초과에만 eatApple을 할 수 있다. //하지만 동기화가 되지않은 경우 쓰레드 3개는 1개가 남았을때 //셋다 1개가남았다고 생각하고 //셋다 eatApple을 하게된다. while (appleStore.getStoredApple() > 0) { appleStore.eatApple(); System.out.println("남은 사과의 수 = " + appleStore.getStoredApple()); } }; for (int i = 0; i < 3; i++) { new Thread(task).start(); } } } class AppleStore { private int storedApple = 10; public int getStoredApple() { return storedApple; } public void eatApple() { if (storedApple > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } storedApple -= 1; } } }
동기화가 제대로 되지 않기때문에 세 쓰레드 모두 1개이상이 남았다고 생각하고 eatApple메서드를 호출하게된다.
발생한 문제 이다 -1개까지 출력되어버린다.한 쓰레드가 임계영역에 락을 걸게되면, 다른쓰레드들은 접근을 하지 못해 동기화 문제가 해결된다.
public void eatApple() { synchronized (this) { //한쓰레드가 락을 걸면 다른쓰레드들은 접근하지못한다. if(storedApple > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } storedApple -= 1; } } }
- wait 와 notify
synchronized를 통해 임계영역에 진입한 쓰레드는 wait() 메서드를 호출하여 lock을 반납하고 임계영역에서 벗어나, waiting pool로 가게 된다.
notify는 waiting pool에 있는 모든 쓰레드 중에서 임의의 쓰레드만 통지를 받는다.
package week5; import java.util.*; public class W08Wait { public static String[] itemList = { "MacBook", "IPhone", "AirPods", "iMac", "Mac mini" }; public static AppleStore appleStore = new AppleStore(); public static final int MAX_ITEM = 5; public static void main(String[] args) { // 가게 점원 Runnable StoreClerk = () -> { while (true) { //점원은 아이템을 하나하나 적재한다. int randomItem = (int) (Math.random() * MAX_ITEM); appleStore.restock(itemList[randomItem]); try { //0.05초간 sleep Thread.sleep(50); } catch (InterruptedException ignored) { } } }; // 고객 Runnable Customer = () -> { while (true) { try { //0.077동안 sleep Thread.sleep(77); } catch (InterruptedException ignored) { } //아이템 리스트 중 하나를 삼 int randomItem = (int) (Math.random() * MAX_ITEM); //적재된 상품을 가져감 appleStore.sale(itemList[randomItem]); System.out.println(Thread.currentThread().getName() + " Purchase Item " + itemList[randomItem]); } }; //1명의 점원, 두명의 고객 new Thread(StoreClerk, "StoreClerk").start(); new Thread(Customer, "Customer1").start(); new Thread(Customer, "Customer2").start(); } } class AppleStore { private List<String> inventory = new ArrayList<>(); public void restock(String item) { synchronized (this) { //재고가 가득 차면 (5) while (inventory.size() >= W08Wait.MAX_ITEM) { System.out.println(Thread.currentThread().getName() + " Waiting!"); try { //임계영역에서 벗어난다 wait(); // 재고가 꽉 차있어서 재입고하지 않고 기다리는 중! Thread.sleep(333); } catch (InterruptedException e) { e.printStackTrace(); } } // 재입고 inventory.add(item); notify(); // 재입고 되었음을 고객에게 알려주기 System.out.println("Inventory 현황: " + inventory.toString()); } } public synchronized void sale(String itemName) { while (inventory.size() == 0) { //아무 제품이 없을때 System.out.println(Thread.currentThread().getName() + " Waiting!"); try { //고객은 기다림 wait(); // 재고가 없기 때문에 고객 대기중 Thread.sleep(333); } catch (InterruptedException e) { e.printStackTrace(); } } while (true) { //재고가 있으면, // 고객이 주문한 제품이 있는지 확인 for (int i = 0; i < inventory.size(); i++) { if (itemName.equals(inventory.get(i))) { inventory.remove(itemName); notify(); // 제품 하나 팔렸으니 재입고 하라고 알려주기 return; // 메서드 종료 } } // 고객이 찾는 제품이 없을 경우 try { //다시 wait System.out.println(Thread.currentThread().getName() + " Waiting!"); wait(); Thread.sleep(333); } catch (InterruptedException e) { e.printStackTrace(); } } } }
위 코드를간단히 요약하면
1. 먼저 세 쓰레드가 동작한다
2. 고객 쓰레드들은 현재 인벤토리가 없어서 waiting poll에서 대기를 한다
3. 점원쓰레드가 인벤토리를 가득 채우면, 다른 쓰레드를깨운다 락을 반납한다
4. 깨어난 고객쓰레드는 자원에 락을 걸고 있으면 구매, 없으면 notify로 다른 쓰레드를깨운다,
5. 다른쓰레드는 고객이깨어나 다른물건을 살수있거나
직원이 다시 5개가될떄까지 물건을 채운다.
- Lock
Synchronized는 같은 메서드 내에서만 lock를 걸 수 있다는 단점이 있다.
위 제약을 해결하기 위해 lock클래스를 사용할 수 있다.
ReentrantLock(재진입 가능한 lock)
특전 조건에서는 lock을 풀고, 나중에 다시 lock를 받는다.
재진입이 가능한 락이다
락을 획득한 상태에서 또 락을 획득하여 더 깊은 락으로 가게된다라고 생각하면 편하다
그만큼 풀어줄때도 추가한 락 만큼 풀어야한다..
ReentrantReadWriteLock
읽기 lock과 쓰기 lock을 가지고있다, 읽기lock은 다른 쓰레드들도 중복으로 읽기 lock을 가지고있지만
쓰기lock은 단독만 사용 가능하다 , 그리고 읽기 lock중에서는 쓰기 lock을 가지는 것을 허용하지 않는다
StempedLock
ReentrantReadWriteLock 은 데이터를 변경할때만 lock을 건다. >> 작업은 빠르지만, 동기화 문제가 일어날 수 있다. 따라서 변경을할때 충돌이 일어날 가능성이 적을때만 사용한다. - Condition
notify는 특정 쓰레드를 선택하지못한다 그것을 해결한것이 Condition이다
Condition은 특정스레드를 특정 조건으로 만족될때만 부를 수 있다.
wait() & notify대신, await() & signal()을 사용해야한다.
try문에서 재고가 가득 찼다면, 현재 쓰레드는 condition1을 지정하고 await메서드를 호출함으로, condition1에는 점원 이라는 특정한 쓰레드가 지정되게 되며package week5; import java.util.concurrent.locks.*; import java.util.*; public class W09Condition { public static final int MAX_TASK = 5; private ReentrantLock lock = new ReentrantLock(); // lock으로 condition 생성 private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private ArrayList<String> tasks = new ArrayList<>(); // 작업 메서드 public void addMethod(String task) { lock.lock(); // 임계영역 시작 try { while(tasks.size() >= MAX_TASK) { String name = Thread.currentThread().getName(); System.out.println(name+" is waiting."); try { //condition1을 기다린다. condition1.await(); // wait(); condition1 쓰레드를 기다리게 합니다. Thread.sleep(500); } catch(InterruptedException e) {} } tasks.add(task); //고객들에게 notify를 한다. condition2.signal(); // notify(); 기다리고 있는 condition2를 깨워줍니다. System.out.println("Tasks:" + tasks.toString()); } finally { lock.unlock(); // 임계영역 끝 } } }
기다리고 있던 나머지 쓰레드들은 condition2로 지정하고, 고객 쓰레드로 묶을수있다..
모던자바(자바8)
- 자바 함수의 변화
함수를 일급 값으로(함수를 객체나 변수처럼쓴다)
일급 객체란, 함수의 인자로 전달하거나, 함수의 결과로 반환할수있는 int, string같은 기본타입이나 객체이다.
그에반해 메서드는 자유롭게 변수엦 저장하거나, 다른 함수의 인자로 전달할 수 없다.
자바8부터는 메서드 참조 기능을통해 메서드를 일급 객체처럼 다루어, 메서드를 다른함수의 인자로 전달하거나 결과로 반환할수 있게 되었다 - 람다 : 익명 함수
이름이 없는 함수라는뜻으로, 함수를 직접 변수에 할당하거나, 다른 함수의 인자로 전달할 수 있다.
예를들어 (x,y) -> x+y처럼 두 수를 더하는 간단한 함수를 나타낸다, 이름이 없으며 직접 변수에 저장하거나, 다른 함수의 인자로 사용할 수 있다. - 스트림
복잡한 반복 없이도 데이터 집합을(예를들면 컬렉션) 쉽게 다룰 수 있다
원본 데이터를 변경하지 않고
쓴 이후는 남지않는다
스트림은 컬렉션에 상속되어 있으며, 모든 컬렉션을 상속하는 구현체들은 스트림을 사용 할 수 있다.
List<Car> benzParkingLot = // carsWantToPark의 스트림값을 받아와서 carsWantToPark.stream() // 거기 구현되어 있는 filter()메서드를 사용합니다. // filter메서드는 함수를 파라미터로 전달받습니다. // 여기서 함수는 제조사가 벤츠면 true를 반환하는 함수네요. // 필터 메서드는 이름처럼 false를 반환한 스트림의 원소들을 제거합니다. .filter((Car car) -> car.getCompany().equals("Benz")) // 이 결과도 반환을 받아서 다시 리스트로 묶어줍니다. .toList();
람다식을 이용해서 특정 원소를 제거할수 있고
반환된 원소를 다시 리스트로 묶어준것을
benzparkinglot에 저장한다
아래는 원래코드
ArrayList<Car> benzParkingLotWithoutStream = new ArrayList<>(); for (Car car : carsWantToPark) { if (car.getCompany().equals("Benz")) { benzParkingLotWithoutStream.add(car); } }
filter는 아주 간단하게 구현 할 수 있다.
//스트림을 받아오기 (.stream()) carsWantToPark.stream() //스트림 가공하기 .filter((Car car) -> car.getCompany().equals("Benz")) //스트림 결과 만들기 .toList();
filter와 map의 차이
filter : 조건에 맞는 것만 반환한다 >> 벤츠만 뽑아온다
map : 모든 요소를 가공해서 반환한다. >> list의 모든 원소를 두배한다.
forEach와//forEach() List<String> carNames = Arrays.asList("Series 6", "A9", "Ionic 6"); carNames.stream() .forEach(System.out::println); //map() carNames.stream() .map(name -> name.toUpperCase()).toList(); // 결과 // ["SERIES 6", "A9", "IONIC 6"]
map메서드도 사용할 수 있다.
forEach는 원소에서 넘겨받은 함수를 실행해주고
map은 원소의 값을 변환시키는데 주로 사용한다. - NULL
public class NullIsDanger { public static void main(String[] args) { SomeDBClient myDB = new SomeDBClient(); String userId = myDB.findUserIdByUsername("HelloWorldMan"); System.out.println("HelloWorldMan's user Id is : " + userId); } } class SomeDBClient { public String findUserIdByUsername(String username) { // ... db에서 찾아오는 로직 String data = "DB Connection Result"; if (data != null) { return data; } else { return null; } } }
userid 에서 HelloWorldMan을 찾을때
data를 반환하거나 null을 반환하는데
그럴때 널을 참조해야한다. >> nullpointerException이 발생한다
개선 - 객체를 감싸서 반환하기
NullIsDanger에서 mydb 객체를 생성하고// 개선 2: 결과값을 감싼 객체를 만듭니다. class SomeObjectForNullableReturn { private final String returnValue; private final Boolean isSuccess; SomeObjectForNullableReturn(String returnValue, Boolean isSuccess) { this.returnValue = returnValue; this.isSuccess = isSuccess; } public String getReturnValue() { return returnValue; } public Boolean isSuccess() { return isSuccess; } } public class NullIsDanger { public static void main(String[] args) { SomeDBClient myDB = new SomeDBClient(); // 개선 2 : 이제 해당 메서드를 사용하는 유저는, 객체를 리턴받기 때문에 더 자연스럽게 성공여부를 체크하게 됩니다. SomeObjectForNullableReturn getData = myDB.findUserIdByUsername("HelloWorldMan"); if (getData.isSuccess()) { System.out.println("HelloWorldMan's user Id is : " + getData.getReturnValue()); } } } class SomeDBClient { // 개선 2 : 결과값을 감싼 객체를 리턴합니다. public SomeObjectForNullableReturn findUserIdByUsername(String username) { // ... db에서 찾아오는 로직 String data = "DB Connection Result"; if (data != null) { return new SomeObjectForNullableReturn(data, true); } else { return new SomeObjectForNullableReturn(null, false); } } }
SomeObjectForNullableReturn객체를 갖고있다.
if (data != null) {
return new SomeObjectForNullableReturn(data, true);
} else {
return new SomeObjectForNullableReturn(null, false);
}
데이터가 있을때와 null일때의 생성자를 지정하여
SomeObjectForNullableReturn메서드에서 새 객체를 생성해주고
if (getData.isSuccess()) {
System.out.println("HelloWorldMan's user Id is : " + getData.getReturnValue());
}
데이터가 있다면... 출력하라 라는방법이 된다. - Optional 객체
위 메서드를 아래와 같이 제네릭을 사용하면 모든 메서드에 사용할 수 있게된다
SomeObjectForNullableReturn(T returnValue, Boolean isSuccess) {
this.returnValue = returnValue;
this.isSuccess = isSuccess;
}
아래 방법을 Optional객체라고 부르며 nullPointerException을 방지해준다
null이 올 수 있는 값을 감싸주어 wrapper 클래스이다
강의 - 5주차과제
5주차 과제는 아직 stream을 이해하지 못했기때문에
강의자료를 계속 보면서 했다
List<Car> benzParkingLot =
// carsWantToPark의 스트림값을 받아와서
carsWantToPark.stream()
// 거기 구현되어 있는 filter()메서드를 사용합니다.
// filter메서드는 함수를 파라미터로 전달받습니다.
// 여기서 함수는 제조사가 벤츠면 true를 반환하는 함수네요.
// 필터 메서드는 이름처럼 false를 반환한 스트림의 원소들을 제거합니다.
.filter((Car car) -> car.getCompany().equals("Benz"))
// 이 결과도 반환을 받아서 다시 리스트로 묶어줍니다.
.toList();
대충 이코드만 그대로 변환해서 쓰면될거같았다
과제 1 : 카테고리가 여행인 책 제목 조회
강의 자료랑 비슷해서 괜찮았다.
과제 2 : 16,200원 이하인 책 제목 조회
getPrice를 해서 16200원 이하인것을 가져왔다
과제 3 : 책 제목에 경제 라는 용어가 들어간 책 제목 조회
contains메서드를 사용하면 된다
과제 4 : 가격이 가장 비싼 책 조회
여기서 부터 조금 막히기 시작했는디
이렇게 구현할시
optional뭐시기가 보이는걸 봐서는 optional객체를 반환한것 같다
그래도.. .답에 가깝지 않을까!
무심코 더블이라고 쳐봤는데
인텔리제이가 자동으로 보여준거임 ... ㄷㄷ
정답이었다..
double books =
bookList.stream()
.filter((Book book) -> book.getCategory().equals("IT")).toList()
.stream().mapToDouble(Book::getPrice).sum();
System.out.println(books);
위 코드는 과제 1번과 과제 4번의 조합으로 사용하였다
과제 5번
검색을 해도 도저히 답이 떠오르지 않던 나 그래서 답지를 봤다
// IT 책 할인 이벤트!!
// 카테고리가 IT 인 책들의 가격을 40% 할인하여 새로운 책 리스트 만들기, discountedBookList
List<Book> discountedBookList = bookList.stream().filter(book -> book.getCategory().equals("IT"))
.map(book -> {
book.setPrice(book.getPrice() * 0.6);
return book;
}).toList();
// List<Book> discountedBookList = bookList.stream().filter(book -> book.getCategory().equals("IT"))
// .peek(book -> book.setPrice(book.getPrice() * 0.6)).toList();
for (Book book : discountedBookList) {
System.out.println("할인된 책 제목: " + book.getBookName());
System.out.println("할인된 책 가격: " + book.getPrice() + "\n");
}
어우 어우
내일 모던자바 다시 공부해야겠다..
.map은 list를 반환해준다, 따라서 조건에 맞는 리스트를 뽑을 때 좋아보인다.
mapToDouble는 double 값 하나만 반환해주니, sum이나 max min avg를 구할때 좋을것 같다
참고자료
https://mangkyu.tistory.com/114
자바 개인과제 - LV01
package calculator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
public class App {
public static void main(String[] args)
{
// //과제 1-5 연산결과 "10개"를 저장 할 수 있는 배열을 선언.
// int [] intArr = new int[10];
//과제 1-5 배열의 위치를 알기 위해서 index변수 추가.
// int index = 0;
Scanner sc = new Scanner(System.in);
boolean flag = true;
//과제 1-7 연산결과를 무한히 저장할 수 있는 list 자료형을 사용한다
ArrayList al = new ArrayList<>();
while (flag)
{
//과제1-1 숫자를 입력받을 변수를 선언한다.
int num1 = 0, num2 = 0;
//과제1-2 사칙연산 기호를 전달받을 변수를 선언한다.
char operater = ' ';
//과제1-3 입력받은 정수와, 사칙연산기호를 사용한 결과값을 저장하는 변수를 선언한다
int result = 0;
//과제1-1 스캐너를 사용해서 "양의"정수를 입력받는다.
Systehttp://m.out.print("첫 번째 숫자를 입력하세요: ");
num1 = sc.nextInt();
Systehttp://m.out.print("두 번째 숫자를 입력하세요: ");
num2 = sc.nextInt();
//과제1-2 사칙연산기호를 전달받는다.
Systehttp://m.out.print("사칙연산 기호를 입력하세요: ");
operater = sc.next().charAt(0);
//과제1-3 입력받은 "양의"정수 2개와 사칙연산 기호를 사용하여 연산을 진행한 후 결과를 출력한다.
//제어문을 활용한다!
switch (operater)
{
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case '*':
result = num1 * num2;
break;
case '/':
if (num2 == 0)
{
System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");
}
else
{
result = num1 / num2;
}
break;
default:
System.out.println("잘못된 연산자를 선택하셨습니다.");
break;
}
System.out.println("결과: " + result);
// //과제 1-6 배열이 넘는다면 배열을 한칸씩 밀고 끝에 새 배열을 저장한다
// if (index < intArr.length) {
// intArr[index++] = result;
// } else {
// for (int i = 1; i < intArr.length; i++) {
// intArr[i-1] = intArr[i];
// }
// intArr[intArr.length - 1] = result;
// }
//과제 1-7 list에 연산결과를 저장한다.
al.add(result);
//과제 1-7 remove메서드를 호출해 제일 먼저 저장된 연산 결과를 삭제 한다
System.out.println("가장 먼저 저장된 연산 결과를 삭제하시겠습니까? (remove 입력 시 삭제)");
if (sc.next().equals("remove"))
{
al.remove(0);
}
//과제 1-8 inquiry라는 문자열이 입력되면 저장된 연산 결과를 전부 출력합니다.
//for-each를 쓰면서 예쁘게 작성하기 위해서는 인덱스를 확인할 수 있는 count가 있어야함.
System.out.println("저장된 연산결과를 조회하시겠습니까? (inquiry 입력 시 조회)");
if (sc.next().equals("inquiry"))
{
Systehttp://m.out.print("연산 결과 \n[");
int count = 0;
for(int num : al)
{
Systehttp://m.out.print(num);
if(count
{
Systehttp://m.out.print(", ");
}
count++;
}
System.out.println("]");
}
//과제1-4 반복문을 사용하여, 반복의 종료를 알려주는 exit문자열을 입력하기 전까지 무한으로 계산을 진행하도록 한다.
System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
if (sc.next().equals("exit")) flag = false;
}
sc.close();
}
}
자세한 설명은 주석에...
오늘의 회고
튜터님과 면담때
TIL을 쓰는데 시간이 너무 오래걸리는것 같은 생각이 너무너무 들었다 ㅇㅇ..
근데 튜터님께선 이 쓰는 시간이 본인이 이해를 할 수 있게 적는거면 그것도 공부의 시간으로 보인다라고 하셨다
세상 마상에 지금까지 나는 til쓰는시간으로 느꼈지 공부의 시간으로 느껴본적이없다
근데 솔직히 TIL을 쓰면서 더 쉽게 풀어쓰느라 빡숙하고있는거 같긴했다 학교 시험공부할때도 이렇게 적어서 했으니까.. 그냥 내 학습법이 이렇구나 생각하게되는 계기였다 계속 이렇게 써야지!
해야할일
1. 자바 강의 복습 + 모,, 모던자바..
2. COMPARATOR 찾아보기.
3. 자바 과제 2주차.
4. 객체지향 SOLID
5. 원시타입과 래퍼타입의 차이점과 원시타입을 쓰는 이유
6. sql with, union 공부하기
12시간 몰입했는가?
이번주 중에선 최대였다..! 5주차 강의가 어려워서 계속 보고 정리한듯ㅇㅇ..
'TIL' 카테고리의 다른 글
20240729 본캠프 11일차 TIL (0) | 2024.07.29 |
---|---|
20240726 본캠프 10일차 TIL (0) | 2024.07.26 |
20240724 본캠프 8일차 TIL (0) | 2024.07.24 |
20240723 본캠프 7일차 TIL (0) | 2024.07.23 |
20240722 본캠프 6일차 TIL (0) | 2024.07.22 |