프로그램은 정적인 코드 집합으로, 주로 파일 형태로 저장됩니다. 예를 들어, 우리가 컴퓨터에 설치한 각종 소프트웨어들이 프로그램입니다. 프로그램은 실행파일이나 소스 코드로 존재하며, 이 자체로는 동작하지 않습니다. 사용자가 실행 명령을 내리면 프로세스가 생성되어 컴퓨터가 프로그램을 실제로 수행하게 됩니다.
프로세스는 실행 중인 프로그램을 의미하며, 운영 체제는 각 프로세스가 독립적으로 실행될 수 있도록 CPU, 메모리, 디스크와 같은 자원을 할당합니다. 각 프로세스는 독립된 메모리 공간을 가지므로 다른 프로세스와 메모리를 공유하지 않으며, 이러한 분리는 보안성과 안정성을 높입니다.
스레드는 프로세스 내에서 실행되는 작은 작업 단위입니다. 스레드는 프로세스의 메모리 공간과 자원을 공유하면서 실행되므로, 하나의 프로세스 내에서 여러 스레드가 동시에 작업을 수행할 수 있습니다. 이를 통해 CPU 사용률을 극대화하고 응답성을 향상시킵니다.
Java에서 스레드를 생성하는 방법은 크게 두 가지입니다.
Thread 클래스 상속
Thread
클래스를 상속하여 run()
메서드를 재정의합니다.Thread
객체에서 start()
메서드를 호출하면 스레드가 실행됩니다.public class Go extends Thread {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("Go : " + i);
}
}
}
Runnable 인터페이스 구현
Runnable
인터페이스를 구현하고 run()
메서드를 정의합니다.Runnable
객체를 Thread
생성자에 전달하여 스레드를 생성할 수 있습니다.public class GoRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("Go : " + i);
}
}
}
public class ThreadMain {
public static void main(String[] args) {
Thread t1 = new Thread(new Go());
Thread t2 = new Thread(new Come());
t1.start();
t2.start();
}
}
public class ThreadMain {
public static void main(String[] args) {
Go go = new Go();
Come come = new Come();
go.start();
come.start();
}
}
public class ThreadMain02 {
public static void main(String[] args) {
// 익명 내부 클래스로 처리
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("Go : " + i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("Come : " + i);
}
}
});
t1.start();
t2.start();
}
}
//람다
public class ThreadMain03 {
public static void main(String[] args) {
Thread t1 = new Thread( () -> {
for (int i = 1; i <= 10; i++) {
System.out.println("Go : " + i);
}
});
Thread t2 = new Thread( () -> {
for (int i = 1; i <= 10; i++) {
System.out.println("Come : " + i);
}
});
t1.start();
t2.start();
}
}
스레드는 생성에서 소멸까지 여러 상태를 거칩니다. 아래는 각 상태의 설명입니다:
start()
메서드가 호출되지 않은 상태입니다.start()
가 호출되면 스레드가 실행 대기 상태에 놓입니다. CPU를 할당받으면 실행 상태로 전환됩니다.join()
, wait()
등이 호출될 때 발생합니다.sleep()
등 시간 제한이 있는 대기 상태입니다.run()
메서드가 종료되면 스레드는 소멸 상태가 됩니다.yield()
: 실행 중인 스레드를 일시적으로 멈추고 다른 스레드에게 CPU를 양보할 때 사용됩니다.sleep()
: 일정 시간 동안 스레드를 일시정지합니다.wait()
및 notify()
: 스레드 간 동기화에 사용되어, 특정 조건이 충족될 때까지 대기 또는 재개하게 합니다.interrupt()
: 다른 스레드에서 해당 스레드를 중단할 때 사용합니다.resume()
, join()
, stop()
: 스레드 실행 상태를 변경하거나 종료하는 데 사용됩니다.이처럼 Java의 스레드는 생성부터 종료까지 여러 상태를 거치며, 다양한 메서드를 통해 상태 전이와 동기화를 제어할 수 있습니다.
[실습] 단수를 입력 받아서 구구단을 출력하는 스레드 클래스 생성
public class Gugudan extends Thread {
private int dan;
public Gugudan(int dan) {
this.dan = dan;
}
@Override
public void run() {
for (int i = 1; i < 10; i++) {
System.out.printf("%s X %s = %s%n", dan, i, (dan * i));
}
}
}
package com.test1;
public class GugudanMain {
public static void main(String[] args) {
Gugudan gugudan = new Gugudan(3);
gugudan.start();
}
}
Gugudan
클래스는 Thread
를 상속받았고, run()
메서드를 오버라이드하여 스레드의 작업 내용을 정의했기 때문에 Gugudan
객체 자체가 스레드 역할을 합니다. 다음은 코드에서 스레드를 사용하는 방식입니다:Gugudan 클래스:
Thread
를 상속하고 run()
메서드를 재정의하였습니다. 이 run()
메서드에는 스레드가 실행할 구구단 출력 작업이 정의되어 있습니다.GugudanMain 클래스:
- Gugudan
객체인 gugudan
을 생성하고, gugudan.start()
를 호출하여 스레드를 시작합니다.
여기서 중요한 점은 gugudan.start()
를 호출하면 스레드가 시작되면서, run()
메서드에 작성된 구구단 출력 작업이 백그라운드에서 실행됩니다. start()
메서드는 새로운 스레드를 생성하고, 그 스레드가 run()
메서드를 실행하도록 합니다.
만약 gugudan.run()
을 직접 호출했다면 단순히 메인 스레드에서 run()
메서드를 실행하는 것이며, 스레드 기능을 사용하지 않는 셈이 됩니다.
public class Gugudan extends Thread {
private int dan;
public Gugudan(int dan) {
this.dan = dan;
}
@Override
public void run() {
// getName()- 스레드 이름
System.out.println(this.getName() + "시작");
for (int i = 1; i < 10; i++) {
System.out.printf("%s X %s = %s%n", dan, i, (dan * i));
}
System.out.println(this.getName() + "끝");
}
}
public class GugudanMain {
public static void main(String[] args) {
Gugudan gugudan1 = new Gugudan(3);
Gugudan gugudan2 = new Gugudan(6);
System.out.println("시작");
// 스레드 이름 정하기
gugudan1.setName("3단");
gugudan2.setName("6단");
gugudan1.start();
gugudan2.start();
System.out.println("끝");
}
}