[java] 스레드

이동엽·2023년 1월 11일
0

스레드란 무엇인가?

프로세스,스레드

프로세스 : 운영체제로부터 실행에 필요한 만큼 메모리를 할당 받아 실행 중인 애플리케이션이라고 의미합니다.

  • 데이터,컴퓨터 자원,스레드로 구성되며,
  • 스레드는 데이터,프로세스가 확복한 자원을 활용해 소스 코드를 실행합니다.
    ->스레드 : 하나의 코드 실행 흐름

메인 스레드

프로세스가 실행되면 메인 스레드가 main 메소드를 실행 해준다.

  • 순차적으로 실행시키고 끝이 아거나 return문을 만나면 종료시킵니다.

멀티 스레드

하나의 프로세스는 여러 개의 스레드를 가질수 있고, 이걸 멀티 스레드 프로세스라고 한다.

  • 여러 스레드로 동시에 작업을 수행할수 있고, 이를 멀티 스레딩이라고 부른다.
  • 예를 들어 크롬을 켜서 카톡으로 사진을 업로드 하고 이런 여러가지 작업을 동시에 해주는것이다.

작업 스레드 생성,실행

  • 메인 스레드외에 다른 작업 스레드를 쓰는건
    ->작업 스레드가 수행할 코드를 작성하고, 작업 스레드를 생성하여 실행시키는것이다.

run()메소드는 스레드가 처리할 작업을 작성하도록 규정되어있다.
근데 이 메소드는 Runnable 인터페이스와 Thread 클래스에 정의되어져 있다.
이걸 어떻게 생성하고 실행을 하냐?
2가지 방법
1. Runnable 인터페이스를 구현한 객체에서 run()을 구현하여 스레드를 생성해서 실행하는 방법
2. Thread 클래스를 상속받은 하위 클래스에서 run()을 구현하여 스레드를 생성하고 실행하는 방법.

첫번째 방법

public class Test1 {
    public static void main(String[] args){
        //순서 3 객체생성,스레드생성
        Runnable test1 = new ThreadTask1();//객체 생성
        Thread thread1 = new Thread(test1);//Thread 클래스를 인스턴스화하고,객체를 받아서 스레드 생성
        //이걸 한줄로 만들면?
        //Thread thread1 = new Thread(new ThreadTask1());

        //순서 4 작업 스레드를 실행시켜서 run() 내부 코드를 처리한다.
        thread1.start();
        
        for (int i = 0; i < 100; i++) {
            System.out.print("@");
        }
    }
}
//순서 1. Runnable 인터페이스를 구현하는 클래스
class ThreadTask1 implements Runnable{
    //순서 2. run() 메소드 바디에 스레드가 수행할 작업 내용 작성한다.

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print("#");
        }
    }
}

출력해보면 출력하는게 섞여있다. 즉 메인스레드와 작업 스레드가 동시에 병렬로 실행이 된다.
저 코드에 출력하는게 @는 메인스레드, #는 작업스레드다.

두번째 방법

public class Test1 {
    public static void main(String[] args){
        //순서 3: Thread 클래스를 상속받은 클래스를 인스턴스화하여 스레드를 생성
        ThreadTask2 thread = new ThreadTask2();
        //순서 4: 작업 스레드를 실행시켜, run() 내부 코드를 처리한다.
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.print("@");
        }
    }
}

//순서 1 :Thread 클래스를 상속받는 클래스 작성
class ThreadTask2 extends Thread{
    @Override
    public void run() {
        //순서 2: run() 메소드 바디에 스레드가 수행할 작업 내용 작성
        for (int i = 0; i < 100; i++) {
            System.out.print("#");
        }
    }
}

출력해보면 출력하는게 섞여있다. 즉 메인스레드와 작업 스레드가 동시에 병렬로 실행이 된다.
저 코드에 출력하는게 @는 메인스레드, #는 작업스레드다.

둘중 하나를 골라서 사용하면된다.

익명 객체 사용해서 스레드 실행

이전 방법은 객체지향 언어이기때문에 클래스안에 모드를 넣어서 run() 메소드를 썼다면,
이건 익명 객체를 활용해 스레드를 생성하고 실행할수 있다.

이것도 두가지방법이다.

// 첫번째 : 익명 Runnable 구현 객체를 활용하여 스레드 생성
       Thread thread1 = new Thread(new Runnable() {
           public void run() {
               for (int i = 0; i < 100; i++) {
                   System.out.print("#");
               }
           }
       });

       thread1.start();

       for (int i = 0; i < 100; i++) {
           System.out.print("@");
       }
//두번째 : 익명 Thread 하위 객체를 활용한 스레드 생성
       Thread thread2 = new Thread() {
           public void run() {
               for (int i = 0; i < 100; i++) {
                   System.out.print("#");
               }
           }
       };

       thread2.start();

       for (int i = 0; i < 100; i++) {
           System.out.print("@");
       }

스레드 생성 시 주의 사항

  • run() 메소드가 종료되면 스레드는 종료됨
    • 즉,스레드가 계속 살아있게 하려면 run() 메소드 내 무한루프 작성이 필요함
  • 한 번 종료한 스레드는 다시 시작할 수 없음
    • 스레드 객체를 다시 생성하고 start()를 다시 호출해야 함

스레드 이름

메인 스레드는 main 이라는 이름을 가지고, 추가 스레드는 기본으로 Thread-n으로 이름을 가진다.

스레드 이름 조회하기

이름 검색은 스레드 참조값.getName()으로 조회할수 있다.

Thread thread3 = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("Get Thread Name");
            }
        });
           thread3.start();
           //soutv 이 단축키로 할수 있음
        System.out.println("thread3.getName() = " + thread3.getName());
        
        //출력
Get Thread Name
thread3.getName() = Thread-0

Process finished with exit code 0

스레드 이름 설정

이번엔 setName()으로 하면된다

Thread thread3 = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("Get.Set Thread Name");
            }
        });
           thread3.start();
           //soutv 이 단축키로 할수 있음
        System.out.println("thread3.getName() = " + thread3.getName());
        thread3.setName("gogo2");
        System.out.println("thread3.getName() = " + thread3.getName());



/출력값
Get.Set Thread Name
thread3.getName() = Thread-0
thread3.getName() = gogo2

Process finished with exit code 0

스레드 인스턴스 주소값

실행 중인 스레드의 주소값을 사용하는 상황에 Thread 클래스의 정적 메소드 currentThread()를 사용한다.

public class Test1 {
    public static void main(String[] args){

        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });

        thread1.start();
        System.out.println(Thread.currentThread().getName());
    }
}

//출력
main
Thread-0

Process finished with exit code 0

스레드 동기화

다중 스레드일때 발생하며,
2개 이상 스레드 간에 데이터를 공유하게 되면 오류가 납니다.
->값이 이상하게 출력이 되거나, 원하는 출력값이 나오지 않는다.

하나의 스레드가 조작하고 있는 공유자원을 다른 스레드가 접근 하지 못해야 오류가 안난다.
-> 이때 필요한게 스레드 동기화다.

임계 영역,락

임계영역 : 하나의 스레드만 코드를 실행할 수 있는 코드영역
락 : 객체에 접근할 수 있는 권한.

//1. 메소드 전체를 임계 영역으로 지정
public synchronized void calc(){

}
//2. 특정 영역을 임계 영역으로 지정
synchronized(객체의 참조변수){

}

1.메소드 앞에 synchronized를 붙여서 메소드 전체가 임계 영역으로 설정

-> 메소드 전체에 임계 영역으로 지정하면 메소드 호출 했을대 메소드 실행할 스레드는 메소드가 포함된 객체의 락을 얻습니다.

public synchronized boolean withdraw(int money) {
	    if (balance >= money) {
	        try { Thread.sleep(1000); } catch (Exception error) {}
	        balance -= money;
	        return true;
	    }
	    return false;
	}

여기 withdraw()호출되면 이걸 실행하는 스레드는 객체의 락을 얻으며, 반납하기 전까지 다른스레드는 이 메소드를 실행하지 못한다.

2. 메소 내의 코드 일부를 {}로 감싸고 블럭앞에 'synchronized(참조변수)'를 붙이는것이다.

public boolean withdraw(int money) {
			synchronized (this) {
			    if (balance >= money) {
			        try { Thread.sleep(1000); } catch (Exception error) {}
			        balance -= money;
			        return true;
			    }
			    return false;
			}
	}
  • 이 참조변수는 락을 걸고자하는 객체를 참조하는거이며, 이블럭을 synchronized블럭이라고부른다.
  • 쓰레드가 이 블럭 영역 안으로 들어가면 lock을 얻고 벗어나면 반납한다.
profile
씨앗

0개의 댓글