쓰레드는 실행 중인 프로그램 내에서 "또 다른 실행의 흐름을 형성하는 주체"를 의미한다.
class MakeThreadDemo {
public static void main(String[] args) {
Runnable task = () -> { // 쓰레드가 실행하게 할 내용
int n1 = 10;
int n2 = 20;
String anme = Thread.currentThread().getName();
System.out.println(name + ": " + (n1 + n2));
};
Thread t = new Thread(task);
t.start(); // 쓰레드 생성 및 실행
System.out.println("End " + Thread.currentThread().getName());
}
// Runnable void run()
class Task extends Thread {
public void run() { // Thread의 run 메소드 오버라이딩
int n1 = 10;
int n 2 = 20;
String name = Thread.currentThread().getName();
System.out.println(name + ": " + (n1 + n2));
}
}
class MakeThreadDemo2 {
public static void main(String[] args) {
Task t1 = new Task();
Task t2 = new Task();
t1.start();
t2.start();
System.out.println("End " + Thread.currentThread().getName());
}
}
class Counter {
int count = 0; // 공유되는 변수
public void increment() {
count++; // 첫 번째 쓰레드에 의해 실행
}
public void decrement() {
count--; // 두 번째 쓰레드에 의해 실행
}
public int getCount() { return count; }
}
class MutualAccess {
public static Counter cnt = new Counter();
public static void main(String[] args) throws interruptedException {
Runnable task1 = () -> {
for(int i = 0; i < 1000; i++)
cnt.increment(); // 값을 1 증가
};
Runnable task2 = () -> {
for(int i = 0; i < 1000; i++)
cnt.decrement(); // 값을 1 감소
};
Thread c1 = new Thread(task1);
Thread c2 = new Thread(task2);
t1.start();
t2.start();
t1.join(); // t1이 참조하는 쓰레드의 종료를 기다림
t2.join(); // t2가 참조하는 쓰레드의 종료를 기다림
System.out.println(cnt.getCount());
}
} // 결과 -12 or 36 or ...
위 코드의 결과를 봤을때 +1000 과 -1000을 했으니 0으로 생각하는게 당연하다. 하지만 결과는 항상 바뀌게 도출된다. 왜일까?

동일한 메모리 공간에 접근을 하면 쓰레드의 코어가 두개가 있으면 동시에 접근이 가능하다는 것이다.
그 말인즉 thread1이 99를 가져와 1 증가할때 thread2도 99를 가져와 1증가시켜 둘다 100인 값을 다시 저장시킨다는 것이다. 그렇다면 이런 과정을 여러번 일으킨다면 어떻게 될까? 당연히 증가하는 숫자가 매번 다르게 형성될 것이다.
그렇다면 이 문제를 어떻게 해결할까?
둘 이상의 쓰레드가 동일한 변수에 동시에 접근해서 생긴 문제이니,
한순간에 한 쓰레드만 변수에 접근하도록 제한하면 문제는 해결된다. (동기화)
synchronized public void increment() {
count++;
}
synchronized public void decrement() {
count--;
}
이와 같이 synchronized선언이 추가되면 이 메소드는 한순간에 한 쓰레드의 접근만을 허용하게 된다.
예를 들어서 이 메소드를 두 쓰레드가 동시에 호출하면, 조금이라도 빨리 호출한 쓰레드가 메소드를 실행하게 되고, 다른 한 쓰레드는 대기하고 있다가 실행이 마치면 메소드를 실행하게 된다.
하지만, 이 방법을 사용하면 메소드 전체에 동기화를 걸어야 한다는 단점이 있다. 전체에 걸게 되면 지금 당장은 메소드가 2개밖에 없어서 그렇지만, 몇백개의 메소드가 있다고 가정하면 1개의 메소드를 실행할 때 몇백개의 다른 메소드는 대기를 해야하는 비효율적인 방법으로 도출된다.
그렇기 때문에 부분적인 동기화를 하는 방법이 있다.
class Counter {
int count = 0;
public void increment() { // 동기화 블록
sysnchronized(this) {
count++;
}
}
public void decrement() { // 동기화 블록
synchronized(this) {
count--;
}
}
public int getCount() { return count; }
}
여기서 this의 의미는 "이 인스턴스의 다른 동기화 블록과 더불어 동기화하겠다" 라는 뜻이다.
쓰레드의 생성과 소멸은 그 자체로 시스템에 부담을 주는 일이다. 따라서 성능 저하로 이어질수 있다. 그래서 쓰레드 풀이라는것을 만들고 그 안에 미리 제한된 수의 쓰레드를 생성해두고 이를 재활용하는 기술이다.

class ExceutorsDemo {
public static void main(String[] args) {
Runnable task = () -> { // 쓰레드에게 시킬 작업
int n1 = 10;
int n2 = 20;
String name = Thread.currentThread().getName();
System.out.println(name + ": " + (n1 + n2));
};
ExcutorService exr = Excutors.newSingleThreadExecutor();
exr.submit(task); // 쓰레드 풀에 작업을 전달
System.out.println("End" + Thread.currentThread().getName());
exr.shutdown(); // 쓰레드 풀과 그 안에 있는 쓰레드의 소멸
newSingleThreadExecutor
풀 안에 하나의 쓰레드만 생성하고 유지한다.
newFixedThreadPool
풀 안에 인자로 전달된 수의 쓰레드를 생성하고 유지한다.
newCachesThreadPool
풀 안의 쓰레드의 수를 작업의 수에 맞게 유동적으로 관리한다.
Callable & Future