프로세스 : 실행중인 애플리케이션 즉, 애플리케이션을 실행하면 운영체제로부터 실행에 필요한 만큼의 메모리를 할당받는다.
프로세스는 각각 독립된 메모리 영역(Code, Data, Stack, Heap의 구조)을 할당받는다.
프로세스 구성
데이터, 컴퓨터 자원, 스레드
스레드는 데이터와 애플리케이션이 확보한 자원을 활용하여 소스코드를 실행한다.
스레드 : 하나의 코드 실행 흐름


public class ThreadExample1 {
public static void main(String[] args) {
}
}
// Runnable 인터페이스를 구현하는 클래스
class ThreadTask1 implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.print("#");
}
}
}
Runnable 인터페이스를 구현한 객체 ThreadTask1을 만들고, Runnable의 추상 메서드인 run()을 수행할 코드를 적어주었다.
위의 내용을 바탕으로 스레드를 생성 할 수 있다.
public class ThreadExample1 {
public static void main(String[] args) {
// Runnable 인터페이스를 구현한 객체 생성
Runnable task1 = new ThreadTask1();
// Runnable 구현 객체를 인자로 전달하면서 Thread 클래스를 인스턴스화하여 스레드를 생성
Thread thread1 = new Thread(task1);
// 위의 두 줄을 아래와 같이 한 줄로 축약할 수도 있습니다.
// Thread thread1 = new Thread(new ThreadTask1());
thread1.start(); <----------
}
}
스레드를 생성하고 run()메서드 내부의 코드를 실행하기 위해 start()메서드를 호출하여 스레드를 실행시켜 주었다.
public class ThreadExample2 {
public static void main(String[] args) {
}
}
// Thread 클래스를 상속받는 클래스 작성
class ThreadTask2 extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.print("#");
}
}
}
1번과 대부분 같지만 여기서는 Thread 클래스를 상속받았다. 따라서 run()메서들르 오버라이딩 해준다음 메서드 바디에 스레드가 수행할 작업 내용을 작성
이제 1번과 같이 스레드를 생성하는데, 여기서는 Thread 클래스를 직접 인스턴스화 하지 않는다.
public class ThreadExample2 {
public static void main(String[]args) {
// Thread 클래스를 상속받은 클래스를 인스턴스화하여 스레드를 생성
ThreadTask2 thread2 = new ThreadTask2();
thread2.start();
}
}
1번과 동일하게 start(); 메서드를 실행시켜준다.
public class ThreadExample1 {
public static void main(String[] args) {
// 익명 Runnable 구현 객체를 활용하여 스레드 생성
Thread thread1 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.print("#");
}
}
});
thread1.start();
}
}
}
1번의 스레드생성을 1줄로 축약한것을 참고하여 보면,
Thread thread1 = new Thread(new ThreadTask1());Thread 인스턴스를 생성하며 동시에 익명 객체를 생성하고 run(); 메서드를 정의 하고있다.
public class ThreadExample3 {
public static void main(String[] args) {
Thread thread3 = new Thread(new Runnable() {
public void run() {
System.out.println("Get Thread Name");
}
});
thread3.start();
System.out.println("thread3.getName() = " + thread3.getName());
}
}
// 출력결과
Get Thread Name
thread3.getName() = Thread-0
public class ThreadExample4 {
public static void main(String[] args) {
Thread thread4 = new Thread(new Runnable() {
public void run() {
System.out.println("Set And Get Thread Name");
}
});
thread4.start();
System.out.println("thread4.getName() = " + thread4.getName());
thread4.setName("Code States"); <----------이름 설정
System.out.println("thread4.getName() = " + thread4.getName());
}
}
//출력결과
Set And Get Thread Name
thread4.getName() = Thread-0
thread4.getName() = Code States
예제코드
public class ThreadExample3 {
public static void main(String[] args) {
Runnable threadTask3 = new ThreadTask3(); <----------동일한 클래스로 이름이 다른 2개의 스레드를 생성하고있다.
Thread thread3_1 = new Thread(threadTask3);
Thread thread3_2 = new Thread(threadTask3);
thread3_1.setName("김코딩");
thread3_2.setName("박자바");
thread3_1.start();
thread3_2.start();
}
}
class Account {
}
class ThreadTask3 implements Runnable {
Account account = new Account(); <-------위의 Account 클래스의 인스턴스를 생성하여 이용
public void run() { <--------메서드
...
}
}
- 임계영역으로 설정된 객체가 있다.
- 현재 이 객체는 다른 스레드에 의해 작업이 이루지지 않고있다.
- 이때 임의의 스레드 A는 해당 객체에 대한 락을 획득하여 임계영역 내의 코드를 실행할 수 있다.
- 스레드 A가 임계영역 내의 코들르 실행중일 때에는 다른 스레드들은 락이 없으므로 해당 객체의 코드를 실행할 수 없다.
- A가 락을 반납하면 다른 스레드들 중 하나가 락을 획득하여 임계 영역내의 코드를 실행 할 수 있게 된다.
class Account {
...
public synchronized boolean withdraw(int money) { <----------------메서드에 삽입
if (balance >= money) {
try { Thread.sleep(1000); } catch (Exception error) {}
balance -= money;
return true;
}
return false;
}
}
위의 경우에 withdraw()가 호출되면 withdraw()를 실행하는 스레드는 withdraw()가 포함된 객체의 락을 얻는다.
class Account {
...
public boolean withdraw(int money) {
synchronized (this) { <---------------------여기가다르다, 영역에 삽입
if (balance >= money) {
try { Thread.sleep(1000); } catch (Exception error) {}
balance -= money;
return true;
}
return false;
}
}
}
try { Thread.sleep(1000); } catch (Exception error) {}
interrupt()를 사용하여 스레드를 실행 대기 상태로 복귀시키는 것을 대비하여 sleep() 는 반드시 try … catch문을 이용해야 함.
왜냐하면 interrupt()가 호출되면 기본적으로 예외가 발생하기 때문임.