실행중인 하나의 프로그램을 프로세스(Process)라고 한다.
프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)를 할당받아 프로세스가 된다.
프로세스는 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 그리고 쓰레드로 구성되어 있으며, 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이 바로 쓰레드이다.
- 하나의 프로세스 내부에서 독립적으로 실행되는 하나의 작업 단위를 Thread(쓰레드)라고 한다.
- 모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재하며,
- 둘 이상의 쓰레드를 가진 프로세스를 멀티 프로세스라고 한다.
메신저로 채팅을 하면서 파일을 다운로드 받을 수 있는 것은 멀티 쓰레드로 작성되었기 때문이다.
Java에서 쓰레드를 구현하는 방법은 두가지가 있다.
public class ThreadBasic {
public static void main(String[] args) {
// 1. Thread Class를 상속받는 방법
Thread t1 = new MyThread1(); // 쓰레드의 생성
t1.start(); // 쓰레드의 실행. run()이 종료되면 MyThread1 소멸된다.
// 2. Runnable 인터페이스를 구현하는 방법
Thread t2 = new Thread(new MyThread2());
t2.start(); // 쓰레드의 실행. run()이 종료되면 MyThread2 소멸된다.
}
}
// 1. Thread Class를 상속받는 방법
class MyThread1 extends Thread {
@Override
public void run() {
//작업할 내용
}
}
// 2. Runnable 인터페이스를 구현하는 방법
class MyThread2 implements Runnable {
@Override
public void run() {
//작업할 내용
}
}
|
Thread Class를 상속받으면 다른 클래스를 상속 받을 수 없기때문에,
Runnable 인터페이스를 구현하는게 일반적인 방법이다.
Runnable 인터페이스는 run()만 정의되어 있는 간단한 인터페이스이다.
Runnable 인터페이스를 구현한 경우, Runnable 인터페이스를 구현한 클래스의 인스턴스를 생성한 다음, 이 인스턴스를 Thread 클래스의 생성자의 매개변수로 제공해야 한다.
쓰레드를 생성했다고 해서 자동으로 실행되는 것은 아니다. start() 메소드를 호출해야만 쓰레드가 실행된다.
쓰레드 동작시 run()이 자동적으로 호출된다.
main 메소드의 작업을 수행하는 것도 쓰레드이며, 이를 main 쓰레드라고 한다.
작업할 내용에는 무한 루프 while(flag) { } 를 사용하여 쓰레드의 동작을 제어하도록 한다.
thread를 중단시킬 수 있는 키 값(flag)을 꼭 세팅하여 메인 쓰레드가 종료될 때 같이 종료되도록 하여야 한다.
예제1) 쓰레드 기본 동작 확인
public class ThreadEX1 {
static long startTime = 0;
public static void main(String[] args) { // ==> main 메소드의 코드를 수행하는 쓰레드
// 모든 프로세스(실행중인 프로그램)는 최소한 하나의 쓰레드(프로세스 내에서 실제 작업을 수행)를 갖고 있다.
// 대부분의 프로그램이 멀티쓰레드로 작성되어 있다.
// 멀티쓰레드 장점 : 1. 시스템 자원의 효율적 사용
// 2. 사용자에 대한 응답성 향상(채팅 중에 파일 전송)
// 3. 작업이 분리되어 코드가 간결해진다.
// 멀티쓰레드 단점 : 1. 동기화에 주의해야 한다. 2. 교착상태가 발생하지 않도록 해야 한다.
// 3. 각 쓰레드가 효율적으로 고르게 실행될 수 있게 해야 한다.
// 쓰레드를 생성한 후에 start()를 호출해야 쓰레드가 작업을 시작한다.
ThreadEx1_A t1 = new ThreadEx1_A(); // 쓰레드의 생성
Runnable r = new ThreadEx1_B();
Thread t2 = new Thread(r);
t1.start(); // 쓰레드 t1을 실행시킨다. ==> 새로운 호출스택 생성
t2.start(); // 쓰레드 t2를 실행시킨다. ==> 새로운 호출스택 생성
// 쓰레드는 순차적으로 실행되지 않고 섞여서 실행된다.
// 실행순서는 OS 스케줄러가 결정한다.
// 쓰레드는 '사용자 쓰레드' 와 '데몬 쓰레드' 두 종류가 있다.
// 실행중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.
startTime = System.currentTimeMillis();
try {
t1.join(); // main쓰레드가 t1의 작업이 끝날 때까지 기다린다.
t2.join(); // main쓰레드가 t2의 작업이 끝날 때까지 기다린다.
} catch(InterruptedException e) {}
System.out.print("소요시간:" + (System.currentTimeMillis() - ThreadEX1.startTime));
}
}
class ThreadEx1_A extends Thread { // 1. 쓰레드 Class 상속
public void run() { // Thread 클래스의 run()을 오버라이딩
for(int i=0; i < 300; i++) {
//System.out.println(getName()); // 조상인 Thread의 getName()을 호출
System.out.print(new String("-"));
}
}
}
class ThreadEx1_B implements Runnable { // 2. Runnable 인터페이스 구현
public void run() { // Runnable 인터페이스의 추상메소드 run()을 구현
// run() : 쓰레드가 수행할 작업을 작성
for(int i=0; i < 300; i++) {
// Thread.currentThread() - 현재 실행중인 Thread를 반환한다.
//System.out.println(Thread.currentThread().getName());
System.out.print(new String("|"));
}
}
}
|
예제2) 싱글 쓰레드
public class SingleThread {
public static void main(String[] args) {
// main 쓰레드 내에서 for 문이 순차적으로 실행되는 걸 확인할 수 있다.
long startTime = System.currentTimeMillis();
for(int i=0; i < 300; i++)
System.out.printf("%s", new String("-"));
System.out.print("소요시간1:" +(System.currentTimeMillis()- startTime));
System.out.println(); // 줄바꿈 표시 목적
for(int i=0; i < 300; i++)
System.out.printf("%s", new String("|"));
System.out.print("소요시간2:"+(System.currentTimeMillis() - startTime));
}
}
|
예제3) 멀티쓰레드
public class MultiThread {
static long startTime = 0;
public static void main(String[] args) {
// 실행할 때마다 출력되는 두 작업의 결과가 섞이는 걸 확인할 수 있다.
// 두개의 작업이 번갈아 가면서 실행될 때 context switching이 발생하여
// 멀티쓰레드시 작업시간이 싱글쓰레드보다 약간 더 소요된다.
// 하지만 동시 작업 수행이 가능하다(채팅 중에서 파일 다운로드)
ThreadEx_C t1 = new ThreadEx_C();
t1.start();
startTime = System.currentTimeMillis();
for(int i=0; i < 300; i++)
System.out.printf("%s", new String("-"));
System.out.print("소요시간1:" + (System.currentTimeMillis() - MultiThread.startTime));
}
}
class ThreadEx_C extends Thread {
public void run() {
for(int i=0; i < 300; i++)
System.out.printf("%s", new String("|"));
System.out.print("소요시간2:" + (System.currentTimeMillis() - MultiThread.startTime));
}
}
|
main thread
- 모든 자바 프로그램은 메인 쓰레드가 main() 메소드를 실행하면서 시작한다.
- return 을 만나면 실행을 종료한다.
- main 쓰레드는 작업 쓰레드를 만들어서 병렬로 코드를 실행할 수 있다.
작업 쓰레드를 만들려면, Thread 클래스를 상속하거나 Runnable 인터페이스를 구현한다.
- 멀티 쓰레드는 실행중인 쓰레드가 하나라도 있다면, 프로세스는 종료되지 않는다.
- 멀티 쓰레드 작업 시에는 각 쓰레드 끼리 정보를 주고받을 수 있어 처리 과정의 오류를 줄일 수 있다.
- 프로세스끼리는 정보를 주고 받을 수 없다.
데몬(daemon) 쓰레드
- 일반 쓰레드(non-daemon thread)의 작업을 돕는 보조적인 역할을 수행한다.
- 일반 쓰레드가 모두 종료되면 자동적으로 종료된다.
- 가비지 컬렉터, 자동저장, 화면자동갱신 등에 사용된다.
- 무한루프와 조건문을 이용해서 실행 후 대기하다가 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.
- setDaemon(boolean on)은 반드시 start()를 호출하기 전에 실행되어야 한다.
public class DaemonThreadEx implements Runnable {
static boolean autoSave = false;
public static void main(String[] args) {
Thread t = new Thread(new DaemonThreadEx()); // Thread(Runnable r)
// run() 메소드를 가지고 있는 클래스의 객체를 매개변수로 준다.
t.setDaemon(true); // 이 부분이 없으면 종료되지 않는다.
t.start();
for(int i=1; i <= 20; i++) {
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
}
System.out.println(i);
if(i == 5) autoSave = true;
}
System.out.println("프로그램을 종료합니다.");
}
public void run() {
while(true) { // 무한루프
try {
Thread.sleep(3 * 1000); // 3초 마다
} catch(InterruptedException e) {
}
// autoSava 의 값이 true 이면 autoSave()를 호출한다.
if(autoSave) autoSave();
}
}
private void autoSave() {
System.out.println("작업 파일이 자동 저장되었습니다.");
}
}
|
쓰레드의 생명 주기(Java Thread Life Cycle and Thread States)
1. New
- 객체 생성
- 쓰레드가 만들어진 상태로 아직 start() 메소드가 호출되지 않은 상태다.
2. Runnable (실행대기)
- 쓰레드가 실행되기 위한 준비단계이다.
- 코드 상에서 start() 메소드를 호출하면 run() 메소드에 설정된 쓰레드가 Runnable 상태로 진입한다.
3. Running (실행상태)
- CPU를 점유하여 실행하고 있는 상태이며 run() 메서드는 JVM만이 호출 가능하다.
- Runnable(준비상태)에 있는 여러 쓰레드 중 우선 순위를 가진 쓰레드가 결정되면
- JVM이 자동으로 run() 메소드를 호출하여 쓰레드가 Running 상태로 진입한다.
4. Terminated (실행종료)
- Running 상태에서 쓰레드가 모두 실행되고 난 후 완료 상태다.
- run() 메소드 완료시 쓰레드가 종료되며, 그 쓰레드는 다시 시작할 수 없게 된다.
5. Blocked (일시 정지)
- 사용하고자 하는 객체의 Lock이 풀릴 때까지 기다리는 상태다.
- wait() 메소드에 의해 Blocked 상태가 된 쓰레드는 notify() 메소드가 호출되면 Runnable 상태로 간다.
- sleep(시간) 메소드에 의해 Blocked 상태가 된 쓰레드는 지정된 시간이 지나면 Runnable 상태로 간다.
join() 메소드는 다른 쓰레드의 종료를 기다리는 것이다.
계산 작업을 하는 쓰레드가 모든 계산 작업을 마쳤을 때, 계산 결과값을 받아 이용하는 경우에 주로 사용한다.
public class JoinMain {
public static void main(String[] args) {
JoinWorkerThread thread = new JoinWorkerThread();
thread.start(); // 작업 쓰레드 시작
System.out.println("Thread가 종료될때까지 기다린다.");
try {
// 별도의 쓰레드에서 처리한 작업이 완료될 때까지 기다려야 하는 경우
thread.join(); // 해당 작업 쓰레드가 종료될 때까지 main 쓰레드가 대기한다.
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread가 종료되었다.");
}
}
class JoinWorkerThread extends Thread {
@Override
public void run() {
for(int i = 0; i < 5; i++){
System.out.println("JoinWorkerThread : "+ i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
|
실행 결과
Thread가 종료될때까지 기다린다.
JoinWorkerThread : 0
JoinWorkerThread : 1
JoinWorkerThread : 2
JoinWorkerThread : 3
JoinWorkerThread : 4
Thread가 종료되었다.
Android 어플을 GitHub 에서 받아서 테스트 해보니 제대로 동작이 잘 안되어 수정해보려고 했으나 쓰레드에 대한 이해가 부족하여 쓰레드 자료/강좌를 찾아서 공부하고 있다.
"이것이 자바다" 유투브 동영상 강좌(https://www.youtube.com/watch?v=1mut1RwpEvw)에 설명이 잘 되어 있다.
Do IT 안드로이드 앱 프로그래밍 책 저자의 동영상을 들었을 때, 자바를 몰라도 안드로이드 어플 만들 수 있다고 한다.
물론 간단한 앱 테스트하는 것은 얼마든지 만들 수 있다.
하지만 응용을 해야 한다거나, 난이도 있는 어플을 만들려고 하면 그 분야의 지식을 깊게 만들어야 어플이 문제없이 동작된다는 걸 절감하게 된다.
'안드로이드 > Java 문법' 카테고리의 다른 글
Java Thread synchronized (동기화) (0) | 2019.11.09 |
---|---|
Java Thread 상태 제어 - 안전한 종료 (0) | 2019.11.08 |
java TCP 소켓 프로그래밍 예제 (0) | 2019.11.06 |
HashMap String keySet 을 String 배열로 변환하는 방법 (0) | 2019.10.22 |
Convert HashMap To ArrayList (0) | 2019.10.21 |