Runnable은 파라미터도 없고 리턴 값도 없는 함수형 인터페이스이다. -> run()@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
Runnable을 구현하여 Thread의 생성자 파라미터로 사용한다.Runnable 인스턴스로 생성한 Thread들은 병렬적이지 않고 직렬적이다.class MyRunnable implements Runnable{
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        String threadGroup = Thread.currentThread().getThreadGroup().getName();
        for(int i = 0; i < 5; i++) {
            System.out.println("[" + threadGroup + "] " + threadName + ": " + i);
        }
    }
}
Runnable r1 = new MyRunnable();
Runnable r2 = new MyRunnable();
Runnable r3 = new MyRunnable();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
Thread t3 = new Thread(r3);
t1.setName("t1");
t2.setName("t2");
t3.setName("t3");
t1.start();
t2.start();
t3.start();
[main] t2: 0
[main] t2: 1
[main] t3: 0
[main] t3: 1
[main] t3: 2
[main] t1: 0
[main] t1: 1
[main] t1: 2
[main] t1: 3
[main] t1: 4
[main] t3: 3
[main] t3: 4
[main] t2: 2
[main] t2: 3
[main] t2: 4
Process finished with exit code 0
Name : Signal Dispatcher(데몬)
소속그룹 : system
Name : AutoSaveThread(데몬)
소속그룹 : main
Name : main(주)
소속그룹 : main
Name : Attach Listener(데몬)
소속그룹 : system
Name : Finalizer(데몬) - 가비지 컬렉터 담당.
소속그룹 : system
Name : Reference Handler(데몬)
소속그룹 : system
main() 함수 호출은 main 스레드 그룹에 속한 main 스레드를 실행하는 것이다. 따라서 main 스레드 그룹에서 시작한다.interrupt()가 가능하다.Thread는 Runnable을 implements 하여 run()이 존재한다.Thread 를 extends 하고 run()을 오버라이딩 해도 된다.Thread는 start()에 의하여 실행된다.public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
            
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
start()를 하면 해당 스레드를 스레드 그룹에 추가한다.Thread의 생성자는 다음과 같다.Runnable, name, ThreadGroup, stackSize 설정이 가능하다.class MyThread extends Thread{
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        String threadGroup = Thread.currentThread().getThreadGroup().getName();
        for(int i = 0; i < 5; i++) {
            System.out.println("[" + threadGroup + "] " + threadName + ": " + i);
        }
    }
}
Thread t1 = new MyThread("t1");
Thread t2 = new MyThread("t2");
Thread t3 = new MyThread("t3");
t1.start();
t2.start();
t3.start();
[main] t3: 0
[main] t3: 1
[main] t3: 2
[main] t3: 3
[main] t3: 4
[main] t1: 0
[main] t1: 1
[main] t1: 2
[main] t1: 3
[main] t1: 4
[main] t2: 0
[main] t2: 1
[main] t2: 2
[main] t2: 3
[main] t2: 4
Process finished with exit code 0
Runnable구현하여 생성자를 통해 넘기는 방법처럼 Thread를 상속 받아서 구현할 수 있다.interrupt() 사용isInterrupted() 아닐 때 까지 넣는다.interrupt()하면 while문 빠져나오게 된다.stop()은 안전하지 않아 Deprecated 되어 있음.Thread 상속받는거 보다는 Runnable을 구현하여 생성자로 넘기는 방법이 좋을 것 같다.교착상태(Deadlock)란 둘 이상의 스레드가 서로의 작업이 끝나기만을 기다리며 작업을 더 이상 진행하지 못하는 상태를 의미한다.