1. Java Mutli Threading 주의할 점
- 순서가 보장되지 않는다 -> 여러 Thread가 공통 영역에 동시 접근하여 수정하는 상황 -> 충돌과 일관성 문제 발생
- Thread 동기화 문제 해결 : 먼저 실행 중인 thread가 작업을 끝낸 후 다음 thread가 실행되도록 Synchronized로 해결
public class IncrementRunnable implements Runnable {
private Counter counter;
public IncrementRunnable(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
counter.increment();
System.out.println("Count : " + counter.getCount());
}
}
}
-------------------
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
--------------------
public class JavaMultiThreadingIssue {
public static void main(String[] args) {
Counter counter = new Counter();
Thread thread1 = new Thread(new IncrementRunnable(counter));
Thread thread2 = new Thread(new IncrementRunnable(counter));
Thread thread3 = new Thread(new IncrementRunnable(counter));
thread1.start();
thread2.start();
thread3.start();
}
}
- Java 서버와 멀티 Threading
import java.io.*;
import java.net.Socket;
import java.util.List;
public class RequestHandler implements Runnable {
private Socket clientSocket;
private List<Customer> customerList;
public RequestHandler(Socket clientSocket, List<Customer> customerList) {
this.clientSocket = clientSocket;
this.customerList = customerList;
}
@Override
public void run() {
System.out.println("Client connected");
//TODO : 데이터를 받기 위한 InputStream 생성
InputStream clientInputStream = null; //클라이언트로부터 받아온 스트림
try {
clientInputStream = clientSocket.getInputStream();
ObjectInputStream ois = new ObjectInputStream(clientInputStream); //클라이언트에서 받아온 스트림을 Object로 읽기 위한 준비
//TODO : 데이터를 보내기위한 OutputStream 생성
OutputStream serverOutputStream = clientSocket.getOutputStream();
PrintWriter printWriter = new PrintWriter(serverOutputStream, true);
Customer customer = (Customer) ois.readObject(); //클라이언트에서 보낸 Customer 객체를 새로운 객체로 생성
ListUtils.addList(customerList, customer);
System.out.println("Thread " + Thread.currentThread().getName() + ": " + customer + "가 대기명단에 추가되었음");
//TODO : 클라이언트에게 응답을 보냄
printWriter.println("현재 고객대기명단은 " + customerList);
clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
----------------------------
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class ServerAdvance {
public static void main(String[] args) {
List<Customer> customerList = new ArrayList<>();
try (ServerSocket serverSocket = new ServerSocket(1234))//TODO : 서버 소켓 생성
{
System.out.println("Thread " + Thread.currentThread().getName() + ": Server Started");
while (true) {
try {//TODO : 클라이언트 접속 대기
Socket clientSocket = serverSocket.accept();
Thread request = new Thread(new RequestHandler(clientSocket, customerList));
request.start();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
1. 디자인 패턴이란
- 소프트웨어 디자인 과정 (= 코드 구현 전 설계) 전형적인 해결책
- 디자인 패턴 = 게임 공략법, 전략과 비슷하다. 상황별 최적의 지식 및 노하우.
- 상황에 맞춰서 유동적으로 적용이 필요하다.
- 왜 배워야 하나? 실무에서 의사소통 시 많이 사용됨. Java 프레임워크, 라이브러리 내부에 구현되어 있음.
2. 디자인패턴 둘러보기
- 생성 / 구조 / 행동 패턴으로 나누어짐
- 생성패턴 : 기존 코드의 유연성과 재사용성을 증가시키는 객체 생성 매커니즘
- 구조패턴 : 객체들과 클래스들의 구조를 유연하고 효율적으로 유지하면서 더 큰 구조로 조립하는 방법
- 행동패턴 : 알고리즘 및 객체 간의 책임 할당과 관련
3. 대표 4가지 디자인 패턴
- 싱글톤 패턴 : 단 하나의 인스턴스만 생성 및 공유하여 자원 절약 일관성 유지 목적
- 싱글톤 사용 전
public class FileWriterExampleTest {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
FileWriterExample writer = new FileWriterExample("src/exercise/chapter_60/singleTon/test.txt");
writer.writeToFile("Thread 1: Message 1");
writer.writeToFile("Thread 1: Message 2");
});
Thread thread2 = new Thread(() -> {
FileWriterExample writer = new FileWriterExample("src/exercise/chapter_60/singleTon/test.txt");
writer.writeToFile("Thread 2: Message 3");
writer.writeToFile("Thread 2: Message 4");
});
Thread thread3 = new Thread(() -> {
FileWriterExample writer = new FileWriterExample("src/exercise/chapter_60/singleTon/test.txt");
writer.writeToFile("Thread 3: Message 5");
writer.writeToFile("Thread 3: Message 6");
});
thread1.start();
thread2.start();
thread3.start();
}
}
- 싱글톤 사용 후
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterSingleton {
private static FileWriterSingleton instance = null;
private FileWriter fileWriter;
public FileWriterSingleton() {
try {
this.fileWriter = new FileWriter("src/exercise/chapter_60/singleTon/test.txt");
} catch (IOException e) {
e.printStackTrace();
}
}
public synchronized static FileWriterSingleton getInstance() {
if (instance == null) {
instance = new FileWriterSingleton();
}
return instance;
}
}
---------------------------
public class FileWriterSingletonTest {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
FileWriterSingleton writer = FileWriterSingleton.getInstance();
writer.writeToFile("Thread 1: Message 1");
writer.writeToFile("Thread 1: Message 2");
});
Thread thread2 = new Thread(() -> {
FileWriterSingleton writer = FileWriterSingleton.getInstance();
writer.writeToFile("Thread 2: Message 3");
writer.writeToFile("Thread 2: Message 4");
});
Thread thread3 = new Thread(() -> {
FileWriterSingleton writer = FileWriterSingleton.getInstance();
writer.writeToFile("Thread 3: Message 5");
writer.writeToFile("Thread 3: Message 6");
});
thread1.start();
thread2.start();
thread3.start();
}
}
- 빌더 패턴 : 복잡한 객체의 생성 과정을 단순화, 가독성와 유연성을 높여 객체를 생성
- 빌더패턴 사용 전 객체 생성
public class User {
private String firstName;
private String lastName;
private int age;
private String email;
public User(String firstName, String lastName, int age, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.email = email;
}
}
---------------------------------
public class BuilderTest {
public static void main(String[] args) {
User user1 = new User("John", "Doe", 30, "johndoe@exmaple.com");
System.out.println("빌더 적용 전 : " + user1);
}
}
- 빌더패턴 사용 후 객체 생성
package exercise.chapter_60.builder;
public class User {
private String firstName;
private String lastName;
private int age;
private String email;
private User(UserBuilder userBuilder) {
this.firstName = userBuilder.firstName;
this.lastName = userBuilder.lastName;
this.age = userBuilder.age;
this.email = userBuilder.email;
}
static class UserBuilder { //inner class
String firstName;
String lastName;
int age;
String email;
public UserBuilder() {
}
public UserBuilder firstName(String firstName) {
this.firstName = firstName;
return this;
}
public UserBuilder lastName(String lastName) {
this.lastName = lastName;
return this;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder email(String email) {
this.email = email;
return this;
}
public User build() {
return new User(this);
}
}
}
----------------------------
package exercise.chapter_60.builder;
public class BuilderTest {
public static void main(String[] args) {
User user2 = new User.UserBuilder()
.firstName("John")
.lastName("Doe")
.age(30)
.email("johndoe@exmaple.com")
.build();
System.out.println("빌더 적용 후 : " + user2);
}
}
- 데코레이터 패턴 : 기존 객체 변경 없이 동적으로 기능을 추가, 수정하는 디자인 패턴
public class OrderCoffee {
public static void main(String[] args) {
Beverage coffee = new Coffee();
System.out.println(coffee.getDescription() + ": $" + coffee.cost());
Beverage coffeeWithMilk = new Milk(coffee);
System.out.println(coffeeWithMilk.getDescription() + ": $" + coffeeWithMilk.cost());
Beverage coffeeWithMilkWithSugar = new Sugar(coffeeWithMilk);
System.out.println(coffeeWithMilkWithSugar.getDescription() + ": $" + coffeeWithMilkWithSugar.cost());
Beverage coffeeWithCreamAndMilk = new Milk(new Cream(new Coffee()));
System.out.println(coffeeWithCreamAndMilk.getDescription() + ": $" + coffeeWithCreamAndMilk.cost());
}
}
- 전략 패턴 : 동적으로 교체가능한 전략을 제공, 객체 관계를 유연하게 만드는 디자인 패턴
public class DiscountCalculator {
private DiscountStrategy discountStrategy;
public void setDiscountStrategy(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double calculateDiscount(double amount) {
if (discountStrategy != null) {
return discountStrategy.calculateDiscount(amount);
} else
return 0;
}
}
---------------------------
public class StrategyTest {
public static void main(String[] args) {
DiscountCalculator calculator = new DiscountCalculator();
calculator.setDiscountStrategy(new NewCustomerDiscountStrategy());
double discount1 = calculator.calculateDiscount(10_000);
System.out.println("신규 가입자 할인 금액 : " + discount1);
calculator.setDiscountStrategy(new SeasonDiscountStrategy());
double discount2 = calculator.calculateDiscount(10_000);
System.out.println("시즌 할인 금액 : " + discount2);
calculator.setDiscountStrategy(new ReferenceFriendDiscountStrategy());
double discount3 = calculator.calculateDiscount(10_000);
System.out.println("시즌 할인 금액 : " + discount3);
}
}