딸기말차

[Java] 7. Thread, Collections, 외부 라이브러리 본문

Bootcamp/Java

[Java] 7. Thread, Collections, 외부 라이브러리

딸기말차 2023. 7. 2. 01:27

엔코아 플레이데이터(Encore Playdata) Backend 2기 백엔드 개발 부트캠프 (playdata.io)

 

백엔드 개발 부트캠프

백엔드 기초부터 배포까지! 매력있는 백엔드 개발자 포트폴리오를 완성하여 취업하세요.

playdata.io


1. Thread

thread는 백그라운드에서 실행되는 프로세스의 일종을 의미한다. thread를 비유하자면 일방통행 길을 여러차선으로 나눠서 통과하는 것과 같다. 즉, 작업을 병렬처리 할 수 있게되고 이는 곧 속도의 상승으로 이어진다.

* thread : 백그라운드에서 수행되는 프로세스의 일종
* Java는 기본적으로 thread를 제공해준다. 이것이 main() 메서드이고, 주 스레드라고 부른다.
* 
* thread 사용방법
* 1. thread 상속
* 2. runnable 인터페이스 구현 : public void run(); 이라는 추상메서드가 정의되어있다. -> thread 진입점
* 
* public class Test extends Thread implements Runnable() : 일반클래스는 상속도받고 구현도 가능하긴 하다. 실제로는 잘안쓴다.
* 1. Thread.class : java.base.jar -> java.lang.Thread.class 로 존재
*  public class Thread implements Runnable() {
*  	// 변수 / 생성자
*  	@Override
*  	public void run() { }
*  	// 메서드들
*  }
* 2. thread를 상속받을 경우, 반드시 run() 내부에 실행내용을 override 해야한다.
* 3. thread 클래스를 상속받는 클래스의 객체를 생성
* 4. 생성한 객체를 통해 start()를 호출 했을 경우에만 thread가 구동된다.
* 5. thread는 main thread가 종료되면 자동으로 멈춘다.
* 
* Java의 thread : sw적 병렬처리 방식 / hw적 병렬처리 : 다중 서버를 사용 / 하나의 서버에서 병렬 처리를 하는 것 : gpu
* 
* thread 내부에는 private String name; 이라는 변수가 존재한다.
* 이 변수는 private으로 선언되어있어서 상속을 받아도 변수에 접근하기 위해선 getter, setter가 필요하다.
* 
* thread에 이름 부여
* 1. 파생(자식) 클래스의 생성자 내부에 부모(thread) 클래스의 생성자에 이름을 전달하여 초기화 (super)
* 2. 부모(thread) 클래스의 setName()을 이용하는 방법
* thread 이름 추출 : getName()

thread를 사용하기 위해선 두 가지 방법이 있는데, 첫번째로 thread를 상속받아 사용하는 방법이 있다.

public class ThreadName extends Thread{
	public ThreadName() {
	}
	
	public ThreadName(String name) {
		super(name); // 부모의 private 변수에 값을 전달
	}
	
	public void run() {
		// thread가 해야할 일들
		int i = 0;
		while (i < 20) {
			System.out.println(getName() + ": " + i); // super로 전달한 값을 getter로 가져옴
			i++;
		}
	}
}

thread를 상속받은 클래스의 객체를 main에서 생성해 실행하기 위해선 start()를 사용하면 된다. 기본적으로 프로그램은 위에서부터 한 줄씩 읽어가며 순차 실행을 하지만, 아까 비유했듯 thread는 여러차선을 동시에 가는 것과 같다. 때문에 실행순서가 뒤죽박죽이 된다.

ThreadName threadName1 = new ThreadName("t1");
ThreadName threadName2 = new ThreadName("t2");
ThreadName threadName3 = new ThreadName("t3");

// thread는 각각 움직인다. 떄문에 순차실행이 아니라 출력순서가 뒤죽박죽이다.
threadName1.start();
threadName2.start();
threadName3.start();

thread를 사용하기 위한 두번째 방법은 runnable 인터페이스를 구현하여 사용하는 방법이다. runnable 인터페이스 내부에는 run() 이라는 추상메서드가 구현되어있고, 이를 시작점으로 thread를 사용하면 된다.

public class RunnableClass implements Runnable{
	@Override
	public void run() {
		System.out.println("override된 run() 내부");
	}
	
	public void start() {
		this.run();
	}
}

 

하지만 runnable 인터페이스를 사용할 시 주의할 점이 있다.

Thread 클래스를 상속받아 사용하는 경우 Thread 클래스 내부에 존재하는 start() 를 통해 thread를 실행하게 되는데, 이 때문에 runnable 내부에 start() 메서드를 만들어 사용하게 되면 사실상 Thread 클래스에 대한 개념이 사라지는 것과 마찬가지다.

때문에 start() 는 마음대로 만들면 안되고, Thread 클래스와 runnable 인터페이스는 서로 관계를 지키며 사용해야한다.

RunnableClass runnableClass1 = new RunnableClass();
RunnableClass runnableClass2 = new RunnableClass();
RunnableClass runnableClass3 = new RunnableClass();

// 마치 thread처럼 보인다. 때문에 start() 는 마음대로 만들면안된다. thread에 대한 개념이 사라지기 떄문이다.
runnableClass1.start();
runnableClass2.start();
runnableClass3.start();

// thread 와 runnable interface의 관계를 지키기 위해 이렇게 사용한다.
Thread thread1 = new Thread(runnableClass1);
Thread thread2 = new Thread(runnableClass2);
Thread thread3 = new Thread(runnableClass3);

thread1.start();
thread2.start();
thread3.start();

위쪽의 thread를 사용하면, 실행순서가 뒤죽박죽이 된다. 특히 주스레드인 main이 종료되었는데 다른 thread가 아직 동작 중이라면 그 thread는 강제종료가 되고, 그럼 기껏 만들어둔 기능이 동작하지 않을 것이다. 이를 어떻게 해결해야할까?

* 주 스레드(main 메서드) 를 다른 thread 작업이 종료 할 때까지 대기시키는 방법 : join()
* 단, 주 스레드의 진행 상태를 강제적으로 대기시키기 때문에 예외처리가 필요하다. (Interrupt Exception)
// 주 스레드(main)을 다른 thread 작업이 종료 될 때까지 정지
try {
    threadName1.join();
    threadName2.join();
    threadName3.join();
} catch (InterruptedException e) {
    System.out.println(e.getMessage());
}

위의 코드들에서는 main을 포함한 4개의 thread가 동작하고 있다. 하지만 우리는 여러작업을 동시에 실행 중일때, 분명 특정 thread는 지연실행을 시키고 싶은 경우가 생긴다. 이를 구현하기 위해선 sleep()을 사용하면 된다.

* thread 일정 시간동안 멈추기 : Thread.sleep(1/1000초)
* sleep() 의 시간은 1000 -> 1초
* sleep()은 스레드 진행을 강제적으로 멈추기 때문에 예외처리가 필수이다.

sleep()을 실습하기 위해 ThreadSleep 클래스를 만들어 사용해 main에서 테스트 해보았다. 주의할 점이 있다면, sleep()은 thread를 강제적으로 멈추기 때문에 예외처리를 반드시 해야한다.

public class SleepThread extends Thread {
	public SleepThread() {
	}
	
	public void run() {
		System.out.println("sleep Start");
		
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			System.out.println(e.getMessage());
		}
		
		System.out.println("sleep End");
	}
}
// thread 일정 시간동안 멈추기
SleepThread s = new SleepThread();
s.start();

이를 runnable 인터페이스를 사용해, 내부의 run() 을 구현하여 사용하는 것도 가능하다.

@Override
public void run() {
    while (!timeout) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }
        num++;
        if (count <= num)
            timeout = true;
    }
    System.out.println(Thread.currentThread().getName() + " : " + num); // 현재 구동중인 thread가 누구인지 알아내는 메서드	
}
// implements Runnable을 이용한 Thread.sleep()
RunnableTest runnableTest = new RunnableTest();
new Thread(runnableTest).start();

thread는 익명객체로 선언해 사용도 가능하다. 이렇게하여 메모리 효율을 높일 수 있는 장점이 존재하지만, join()을 사용하기 위해서는 결국 객체를 선언해 사용해야된다는 단점도 존재한다.

 

익명객체 : 프로그램에서 일시적으로 한번만 사용되고 버려지는 객체로, 메모리 효율을 높일 수 있지만 재사용 할 수 없는 객체기 때문에 확장성은 떨어진다.

// 익명객체 : 객체 생성 없이, 바로 메서드에 접근해 사용도 가능하다. 하지만 join() 을 사용하기 위해서는 객체생성이 필요하다.
new Thread(runnableClass1).start();
new Thread(runnableClass2).start();
new Thread(runnableClass3).start();

2.  Collections

Java엔 자료구조들을 구현해 둔 Collections Framework가 존재한다. 우리는 개발을 하면서 많은 데이터를 다루게되고, 이를 위해 자료구조를 사용하는 것은 필수이다. 때문에 Collection은 반드시 알아둬야한다.

* Collections : 자료구조
* 자료구조 : 여러 데이터를 담아두고, 필요할 때마다 꺼내어 사용하기 위한 구조
* 
* Collections의 공통 메서드
* 1. 데이터 담기 : add(), addAll()
* 2. 데이터 확인 : contains(), containsAll(), isEmpty(), equals(), size()
* 3. 데이터 삭제 : clear(), remove(), removeAll()

우선, 가장 대표적인 Collection인 List에 관한 실습을 진행하였다. List 인터페이스는 구현체로 ArrayList와 LinkedList를 가지고 있다.

둘의 기능은 거의 동일하지만, LinkedList는 데이터 중간 값들에 삽입, 삭제가 많이 일어날 경우 더 좋은 퍼포먼스를 가지고 있다는 장점이 있다. 반대로 데이터 끝부분에 삽입, 삭제가 많이 일어나는 경우엔 ArrayList가 더 좋은 퍼포먼스를 보여준다.

ArrayList<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");

list.add(1, "a"); // 특정 위치를 지정하여 데이터를 추가
list.set(2, "f"); // 특정 위치의 데이터를 다른 데이터로 변경
list.remove(0); // 특정 위치의 데이터를 제거
String result = list.remove(0); // 특정 위치의 데이터를 제거하고 반환
int index1 = list.indexOf("f"); // 특정 데이터의 index를 앞에서부터 찾아 반환
int index2 = list.lastIndexOf("f"); // 특정 데이터의 index를 뒤에서부터 찾아 반환

다음으로 Set에 관한 실습을 진행하였다. Set은 집합으로, 중복을 허용하지 않고 데이터의 순서가 없다. 때문에 중복제거에 자주 사용하게 된다.

주의할 점이 있다면 Set의 데이터를 추출하기 위해선 데이터의 형태를 바꿔서 출력해야한다는 점이다. 이를 위해 보통 Iterator 형태로 변환해 많이 사용한다.

* Set interface
* set은 중복을 허용하지않고, 순서가 없다. 즉, 순서없이 데이터만 존재한다.
* 
* Set을 구현받은 class : HashSet implements Set
public static void hashSetTest() {
    Set<String> set = new HashSet<String>();
    HashSet<String> set2 = new HashSet<String>();
    set.add("a"); // 데이터 입력
    set.add("b");
    set.add("c");
    set.add("d");
    System.out.println(set.size()); // 데이터 크기

    // 전체 데이터 추출 : iterator로 변환, Set.iterator()
    Iterator<String> iter = set.iterator();

    while(iter.hasNext())
        System.out.println(iter.next());
}

Set의 추가 실습으로 로또를 만들어 보았다. 로또를 만들기 위해선 난수를 생성해야하는데, 난수를 생성하기 위해선 java의 Ramdom 클래스를 사용하거나, Math 클래스에 있는 random 메서드를 사용하면 된다.

public static void hashSetLotto() {
    HashSet<Integer> lotto = new HashSet<Integer>();

    /*
     * 임의의 수 6개 추출 후 저장
     * 로또번호 : 총 45개
     * Math.random() : 0 ~ 1 사이의 실수, (int)Math.random() * 45 + 1
     * Ramdom.class : nextInt(범위)
     */
    Random random = new Random();
    for (int i = 0; lotto.size() < 6; i++)
        lotto.add(new Integer(random.nextInt(45) + 1));
    System.out.println(lotto);

    List<Integer> lottoList = new LinkedList<Integer>();
    for (int i = 0; i < 6; i++)
        lottoList.add((int)(Math.random() * 45) + 1);
    System.out.println(lottoList);
    Collections.sort(lottoList); // 정렬
    System.out.println(lottoList);
}

마지막으로 Map에 관한 실습을 진행하였다. Map 인터페이스는 구현체로 HashMap을 자주 사용한다. 하지만 HashMap은 synchronized 키워드가 내부에 없기 때문에 멀티스레드 환경에서 사용할 수 없다. 이런 경우엔 ConcurrentHashMap을 구현체로 사용하게 된다.

Map<String, Integer> map = new HashMap<String, Integer>();
// public class HashMap implements Map {} 으로 만들어져 있기 때문에 HashMap으로도 선언가능하다.(실무에선 잘 안쓴다.)
HashMap<String, Integer> map2 = new HashMap<String, Integer>();

map.put("a", 10); // 데이터 추가 (key, value)
map.put("a", 20);
map.put("a", 30); // 이렇게 3개를 넣어도 중복을 허용하지 않기 때문에 (a, 30) 하나만 존재한다.	
map.put("b", 40);

System.out.println(map.size()); // HashMap의 데이터의 개수 확인
System.out.println(map.get("b")); // 데이터 추출

Set<String> set = map.keySet(); // HashMap의 key들만 출력, 반환타입이 Set<데이터타입>이다.

// set데이터를 추출하려면 형태를 바꾸어 출력해야한다.
// 1. LinkedList 형태로 변환
List<String> list = new LinkedList<String>(set);
for (String s : list)
    System.out.println(s);
// 2. Iterator 형태로 변환
// iterator : 열거형, 데이터를 나열만 하기 때문에 index가 없다. 때문에 while과 iterator가 가지고있는 hasNext()를 사용해 데이터를 추출한다.
Iterator<String> iter = set.iterator();
while (iter.hasNext()) // 추출할 데이터가 있는지 여부 (true / false)
    System.out.println(iter.next()); // 데이터를 추출

추가 실습으로 학생 클래스를 만들어, 학생이름과 학생번호를 Map 형태로 저장하였다.

public class Student {

	private int sno;
	private String sname;
	
	public Student() {
	}
	
	public Student(int sno, String sname) {
		this.sno = sno;
		this.sname = sname;
	}

	public int getSno() {
		return sno;
	}

	public void setSno(int sno) {
		this.sno = sno;
	}

	public String getSname() {
		return sname;
	}

	public void setSname(String sname) {
		this.sname = sname;
	}

	@Override
	public String toString() {
		return "Student [sno=" + sno + ", sname=" + sname + "]";
	}
}
public static void hashMapStudent() {
    Student s1 = new Student(10, "test1");
    Student s2 = new Student(20, "test2");
    Student s3 = new Student(30, "test3");
    Student s4 = new Student(40, "test4");

    Map<String, Student> map = new HashMap<String, Student>();
    map.put("a", s1);
    map.put("b", s2);
    map.put("c", s3);
    map.put("d", s4);

    System.out.println(map);
    System.out.println(s1);
    for (Map.Entry<String, Student> entry : map.entrySet())
        System.out.println("key: " + entry.getKey() + " value: " + entry.getValue());

    for (String s : map.keySet())
        System.out.println("key: " + s + " value: " + map.get(s));
}

3.  Thread 실습_1) ARS 모금

3대의 전화에서 ARS 모금이 오는 상황을 전제로 실습을 진행하였다. ARS 모금 전화를 한다 생각하면, 순서대로 모금을 하는 것이 아니라 여러 사람이 마구 전화를 할 것이다. 때문에 이 경우 thread를 반드시 사용해야한다.

 

우선, 모금을 하기 위한 계좌가 필요하기 때문에 Account 클래스를 구현하였다.

/*
 * 각 ARS 번호를 통해 1000씩 누적하는 일처리
 */
public class Account {
	private int total = 0;
	
	public Account() {
	}
	
	public void deposit() {
		this.total += 1000;
	}

	public int getTotal() {
		return total;
	}

	@Override
	public String toString() {
		return "Account [total=" + total + "]";
	}	
}

 

다음으로, 모금을 하는 사람에 대한 클래스를 구현하였다. 주의할 점은, 모금자가 순서대로 전화하며 모금하는 것이 아니기 때문에, Thread 클래스를 상속받아 구현해야한다.

public class Customer extends Thread {
	Account account = null;
	
	public Customer() {
	}
	
	public Customer(Account account, String name) {
		this.account = account;
		setName(name);
	}
	
	public void run() {
		for (int i = 0; i < 200; i++) {
			System.out.println(getName() + " : " + i + "번째 성금자");
			this.account.deposit();
			if (account.getTotal() >= 500000)
				break;
		}
	}
}

구현한 두 클래스를 main에서 호출해 모금을 진행하였다. 모금통은 일반적으로 공통으로 사용되기 때문에 static 영역에 선언해 주었다.

주의해야할 점은 java가 업데이트 되며 기존엔 Thread 클래스의 start()와 join()이 순서 상관없이 작성해도 작동했지만, 이젠 start()를 먼저 호출한 후 join()을 호출해야 제대로 동작한다는 점이다.

/*
 * ARS 모금
 * ARS는 3개 전화: 한번 전화 걸때마다 자동으로 1000씩 모금
 */
public class TVcontribution {
	static Account account; // 모금통을 의미, 때문에 static을 사용
	
	public static void main(String[] args) {
		account = new Account();
		
		Customer customer1 = new Customer(account, "02-777-1001");
		Customer customer2 = new Customer(account, "02-777-1002");
		Customer customer3 = new Customer(account, "02-777-1003");
		try {
			// start를 먼저 실행해야 join이 제대로 실행된다.
			customer1.start();
			customer2.start();
			customer3.start();
			
			customer1.join();
			customer2.join();
			customer3.join();
		} catch (InterruptedException e) {
			System.out.println(e.getMessage());
		}
		
		System.out.println("총 모금액 : " + account.getTotal());
	}
}

4. Thread 실습_2) 은행 입출금

두명이 같은계좌에 입금과 출금을 진행하는 상황을 전제로 실습을 진행하였다. 이 경우 입금과 출금의 순서가 중요해진다.

 

같은 계좌를 사용해 입출금을 시작할 때, 서로 잔액이 다르게 표시될 수 있기 때문이다. 때문에 syncronized 키워드를 사용해, 먼저 실행 된 thread가 있다면 다른 thread의 실행을 막아줘야 한다.

* synchronized 키워드를 붙이면 먼저들어온 것이 실행 될 때 동안 다른 실행을 막는다.
* synchronized는 메서드 선언부에 선언할 수도 있고, 내부에 선언할 수도 있다.
* 왜냐하면 우리가 만든 메서드는 선언부에 선언할 수 있지만, 만약 부모에게서 상속받은 메서드가 있다면 선언부를 변경할 수 없기 때문이다.

우선, 입출금을 하기 위한 은행이 필요하기 때문에 Bank 클래스를 구현하였다. 이때 은행에 있는 돈은 아무나 접근하면 안되는 데이터이기 때문에 private를 사용 후 getter와 setter를 생성해 주었고, 일반적으로 입출금 시 존재하는 처리 시간을 구현하기 위해 sleep()을 사용해 주었다.

public class Bank {
	private int money = 10000;
	
	public Bank() {
	}

	public int getMoney() {
		return money;
	}
	
	public void setMoney(int money) {
		this.money = money;
	}
	
	// 입금 처리 메서드
	public void saveMoney(int money) {
		synchronized(this) {
			try {
				Thread.sleep(3000); // 입금 처리 시간
			} catch (InterruptedException e) {
				System.out.println(e.getMessage());
			}
			this.setMoney(this.money + money); // 현재 잔액 + 입금액
		}
	}
	
	// 출금 처리 메서드
	public synchronized void minusMoney(int money) {
		try {
			Thread.sleep(200); // 출금 처리 시간
		} catch (InterruptedException e) {
			System.out.println(e.getMessage());
		}
		this.setMoney(this.money - money);
	}
}

다음으로 두명의 입출금자를 정의하는 클래스를 구현하였다. 이때, 두명의 입출금자는 순서대로 입출금을 하는 것이 아니기 때문에 Thread 클래스를 상속받아주었다.

public class Me extends Thread {
	public Me() {
	}
	
	public void run() {
		SyncMain.bank.saveMoney(5000);
		System.out.println("입금 후 잔액: " + SyncMain.bank.getMoney());
	}
}
public class Wife extends Thread {
	public Wife() {
	}
	
	public void run() {
		SyncMain.bank.minusMoney(3000);
		System.out.println("출금 후 잔액: " + SyncMain.bank.getMoney());
	}
}

이 후 main에서 구현한 클래스들을 실행하였다. 보통 thread는 코드의 위치에 상관없이 실행되지만 이 경우 synchronized 키워드를 통해 한 thread가 실행되는 동안 다른 thread의 실행을 막았기 때문에, 입금.start()를 출금.start()의 위에 먼저 실행하여 순서대로 실행될 수 있게 하였다.

public class SyncMain {
	static Bank bank;
	
	public static void main(String[] args) {
		bank = new Bank(); // 은행 업무 시작
		System.out.println("현재 잔액: " + bank.getMoney());
		
		Me m = new Me(); // 입금 고객
		Wife w = new Wife(); // 출금 고객
		
		m.start();
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			System.out.println(e.getMessage());
		}		
		w.start();
	}
}

5. Java로 엑셀파일 다루기

Java는 기본적으로 엑셀을 다룰 수 있는 라이브러리가 없다. 때문에 외부 라이브러리를 사용해야한다.

* 이클립스에서 외부 라이브러리 가져오기
* 1. 클래스파일 -> Build path -> Configure Build path -> Libraries -> add external jars
* 2. module-info : requires 외부라이브러리명

기본적으로 엑셀도 파일이기 때문에, 방식은 java의 파일입출력과 동일하다.

우선 엑셀파일을 작성해야 하기 때문에 엑셀파일을 만들어주는 클래스를 구현하였다.

/*
 * 엑셀 파일 생성 및 데이터 추가
 */
public class ExcelMaker {
	public ExcelMaker() {
	}
	
	public static void excelMaker() {
		String uri = "c:/filetest/data.xls";
		
		// 1. File 객체의 생성자를 이용하여 파일 경로를 전달
		File f = new File(uri); // File.class : java.io
		
		// 2. 쓰기 용도의 엑셀(WorkBook) 문서 객체 : jxl.jar
		WritableWorkbook wb = null;
		
		try {
			wb = Workbook.createWorkbook(f); // File 객체를 전달한 엑셀 문서
			WritableSheet s = wb.createSheet("첫 번째", 0); // (시트명, 시트의index)
			
			/* 
			 * 1. 텍스트 : Label.class -> new Lable(열, 행, 글씨);
			 * 2. sheet의 cell메서드 사용 -> s.addCell(Label)
			 */
			for (int i = 0; i < 10; i++) {
				Label label = new Label(0, i, "데이터" + i); // 데이터0
				s.addCell(label);
			}
			wb.write(); // 실제로 write 
			wb.close();	// 저장 및 종료
			System.out.println("엑셀 파일 생성 완료");
		} catch (IOException e) { // 외부파일
			System.out.println(e.getMessage());
		} catch (RowsExceededException e) { // 행에
			System.out.println(e.getMessage());
		} catch (WriteException e) { // 쓰기
			System.out.println(e.getMessage());
		}	
	}
}

이 후 만든 엑셀파일 혹은 가지고있는 엑셀파일을 읽어오는 클래스를 구현 후 main에서 실행시켰다.

public class ExcelReader {
	public ExcelReader() {
	}
	
	public static void excelReader() {
		String uri = "c:/filetest/data.xls";
		File f = new File(uri);
		
		Workbook wb = null;
		try {
			wb = Workbook.getWorkbook(f);
			Sheet s = wb.getSheet(0); // 0번 index의 시트를 가져옴
			int n = 0; 
			while(true) {
				try {
					Cell c = s.getCell(0, n); // 몇 행 몇 열?
					System.out.println(c.getContents());
					n++;					
				} catch (Exception e) {
					break;
				}
			}
			
			wb.close();
		} catch (BiffException e) { // 확장자 오류 : xls, xlsx 중 xlsx는 못읽어서 쓰는 exception
			System.out.println(e.getMessage());
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}
}
ExcelMaker.excelMaker();
ExcelReader.excelReader();

6. 7일차 후기

thread와 Collections는 실무에서 굉장히 중요하고 많이 사용하기 때문에, 반드시 숙지하고 있어야한다. 우리가 실생활에서 사용하는 대부분의 프로그램이 멀티스레드 환경에서 동작하고 있고, 여러 데이터를 다루기 위해선 자료구조 없이는 불가능하기 때문이다.

 

과거에 수업을 들을 땐 thread라는게 있는 것만 알고 제대로 사용해본적이 없었는데, 이번 기회에 thread가 무엇인지, 왜 사용하는지, 어떻게 사용하는지에 대해 알게되었다. 때문에 앞으로 진행할 프로젝트에서 thread를 적극적으로 사용하며 이를 능숙하게 다룰 수 있도록 노력해야겠다고 생각했다.