본문 바로가기
자바(Java)

자바 중급15 - 스레드(쓰레드, Thread) 1 : 생성, 공유객체, 동기화블록(Snchronized)

by codeyaki 2022. 1. 19.
반응형

해당 강의를 듣고 정리한 것입니다

https://programmers.co.kr/learn/courses/9

 

자바 중급

평가 5.0 17개의 평가 ★★★★★17 ★★★★0 ★★★0 ★★0 ★0 ds02168 2021.08.20 15:37 Yeonggwang 2021.06.28 01:48 강신우 2021.04.23 10:20 HyeonWoo Jeong 2021.04.08 17:12 이용준 2021.01.26 19:23 리뷰 더보기

programmers.co.kr


스레드

: 동시에 여러 가지 작업을 수행할 수 있게 하는 것!! (멀티태스킹!!!)

  • 동시에 여러 가지 작업 수행
  • 프로세스(Process) : 현재 실행되고 있는 프로그램
  • 자바 프로그램은 JVM에 실행되는데 이 JVM도 프로그램 중 하나
  • 운영체제 입장에서는 자바도 하나의 프로세스로 실행을 하는 것
  • 워드프로세서가 하나의 프로세스라면,
    하나의 프로세스 안에서도 여러 개의 흐름이 동작할 수 있다. 이것을 스레드(Thread)라고 함.
  • 여러 작업을 동시에 하고 싶게 만들고 싶다면 Thread를 이용

스레드 만들기(extends Thread)

: Thread를 만드는 방법은 크게 Thread클래스를 상속받는 방법과 Runnable인터페이스를 구현하는 방법이 있음

 

Thread를 상속받는 방법

  • java.lang.Thread클래스 상속 그리고 Thread가 가지고 있는 run() 메서드를 오버 라이딩시킨다.
    (예시 : 10번 반복하여 str을 출력)
public class MyThread extends Thread {
	String str;
	public MyThread(String str) {
		this.str = str;
	}
	
	@Override
	public void run() {
		for(int i=0; i<10; i++) {
			System.out.println(str);
			try {
				Thread.sleep((int)(Math.random()*1000));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}
  • Thread클래스를 상속받은 MyThread1을 사용하는 클래스
    - Thread를 상속받았으므로 MyThread1은 Thread임.
    - 스레드를 생성하고, Thread 클래스가 가지고 있는 start() 메서드를 호출한다.
    (run() 메서드를 직접 호출하는 것이 아닌 strart()로 스레드를 시작시키는 것. 그럼 자동으로 run()이 호출됨)
public class MyTreadExam {
	public static void main(String[] args) {
		MyThread t1 = new MyThread("*");
		MyThread t2 = new MyThread("-");
		
		//쓰레스 시작
		t1.start();
		t2.start();
		
		System.out.println("main Thread end !");		
	}
}

 

Runnable인터페이스를 구현해서 스레드를 만드는 방법

  • Runnable인터페이스를 구현해서 스레드를 만드는 방법
    (왜? 자바는 단일 상속만 구현 가능하기 때문에 이미 다른 클래스를 상속받는 경우 스레드를 상속받을 수 없기 때문에 그런 경우에 인터페이스를 이용하여 구현한다!!!!)
    - implements Runnble를 통해 Runnable 인터페이스를 구현시켜준다.
public class MyThread2 implements Runnable {
	String str;
	public MyThread2(String str) {
		this.str = str;
	}
	@Override
	public void run() {
		for(int i=0; i<10; i++) {
			System.out.println(str);
			try {
				Thread.sleep((long)(Math.random()*1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}	
	}
}
  • Runnable 인터페이스를 구현한 MyTread2 사용하기
    - MyThread2는 Thread를 상속받지 않았기 때문에 Thread가 아님
      따라서, Thread를 생성 후, 해당 생성자에 MyThread2를 넣어서 Thread를 생성해준다.
    - Thread클래스가 가진 start() 메서드를 호출한다!
public class MyThreadExam2 {
	public static void main(String[] args) {
		MyThread2 t1 = new MyThread2("*");
		MyThread2 t2 = new MyThread2("-");
		
		//인터페이스를 했기떄문에 Thread의 strat()을 사용할수가 없기 떄문에 쓰레드객체를 생성해야함
		Thread thread1 = new Thread(t1);
		Thread thread2 = new Thread(t2);
		
		thread1.start();
		thread2.start();
		
	}
}

스레드와 공유 객체

: 하나의 객체를 여러 개의 Thread가 사용한다는 것을 의미
MusicBox라는 클래스가 있다고 가정. 해당 클래스는 3개의 메서드를 가지고 있고, 각각의 메서드는 1초 이하의 시간 동안 10번 반복하며, 어떠한 음악을 출력합니다. 이러한 MusicBox를 사용하는 MusicPlayer를 3명 만들어 보도록 하겠습니다.

 

MusicPlayer3명은 하나의 MusicBox를 사용할 것이다. 이때 어떤 일이 발생하는 살펴보도록 하겠습니다.

  • 공유 객체 MusicBox
public class MusicBox {
	public void playMusicA() {
		for(int i=0; i<10; i++) {
			System.out.println("신나는 음악");
			try {
				Thread.sleep((long)(Math.random()*1000));
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		}
		
	}
	public void playMusicB() {
		for(int i=0; i<10; i++) {
			System.out.println("슬픈 음악");
			try {
				Thread.sleep((long)(Math.random()*1000));
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		}
		
	}
	public void playMusicC() {
		for(int i=0; i<10; i++) {
			System.out.println("힙합 음악");
			try {
				Thread.sleep((long)(Math.random()*1000));
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		}
		
	}
}

 

  • MusicBox를 가지는 Thread객체 MusicPlayer
public class MusicPlayer extends Thread {
	int type;
	MusicBox musicBox;
	public MusicPlayer(int type, MusicBox musicBox) {
		this.musicBox = musicBox;
		this.type = type;
	}
	@Override
	public void run() {
		switch (type){
			case 1:
				musicBox.playMusicA();
				break;
			case 2:
				musicBox.playMusicB();
				break;
			case 3:
				musicBox.playMusicC();
				break;
		}	
	}		
}
  • MusicBox와 MusicPlayer를 이용하는 MusicBoxExam1 클래스
public class MusicBoxExam1 {
	public static void main(String[] args) {
		MusicBox box = new MusicBox();
		
		MusicPlayer kang = new MusicPlayer(1, box);
		MusicPlayer kim = new MusicPlayer(2, box);
		MusicPlayer lee = new MusicPlayer(3, box);
		
		kang.start();
		kim.start();
		lee.start();
	}
}

동기화 메서드와 동기화 블록

  • 공유 객체가 가진 메서드를 동시에 호출되지 않도록 하는 방법
    - 메서드 앞에 synchronized를 붙인다.
    - 여러 개의 Thread들이 공유 객체의 메서드를 사용할 때 메서드에 synchronized가 붙어 있을 경우 먼저 호출한 메서드가 객체의 사용권(Monitoring Lock)을 얻는다.
public synchronized void playMusicA() {
    for(int i=0; i<10; i++) {
        System.out.println("신나는 음악");
        try {
            Thread.sleep((long)(Math.random()*1000));
        }catch(Exception e) {
            e.printStackTrace();
        }
        
    }
		
}
  • 메서드 앞에 synchronized를 붙여서 실행하면 메서드 하나가 전부 실행된 뒤 다음 메서드가 실행됨.
  • 해당 모니터링 락은 메서드 실행이 종료되거나, wati() 같은 메서드를 만나기 전까지 유지됨.
  • 다른 쓰레들은 모니터링 락을 놓을 때까지 대기함.
  • synchronized를 붙이지 않은 메서드는 다른 스레드들이 synchronized메서드를 실행하면서 모니터링 락을 획득했다 하더라도, 그것과 상관없이 실행됨
  • synchronized를 메서드에 붙여서 사용할 경우, 메서드의 코드가 길어지면 마지막에 대기하는 스레드가 너무 오래 기다리게 된다. 이를 막기 위해 메서드에 synchronized를 붙이지 않고, 문제가 있을 것 같은 부분만 synchronized블록을 사용한다!
public void playMusicB() {
	for(int i=0; i<10; i++) {
		synchronized (this) {
			System.out.println("슬픈 음악");
		}
		try {
			Thread.sleep((long)(Math.random()*1000));
		}catch(Exception e) {
			e.printStackTrace();
		}	
	}	
}
  • synchronized(this){ } 블록을 이용해 해당 코드가 실행될 때에만 모니터링 락을 얻게 한다!!
  • 그 외의 부분을 실행할 때는 다른 스레드도 작업을 진행한다!!

전체 코드

public class MusicBox {
	public synchronized void playMusicA() {
		for(int i=0; i<10; i++) {
			System.out.println("신나는 음악");
			try {
				Thread.sleep((long)(Math.random()*1000));
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		}
		
	}
	public void playMusicB() {
		for(int i=0; i<10; i++) {
			synchronized (this) {
				System.out.println("슬픈 음악");
			}
			try {
				Thread.sleep((long)(Math.random()*1000));
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		}
		
	}
	public void playMusicC() {
		for(int i=0; i<10; i++) {
			System.out.println("힙합 음악");
			try {
				Thread.sleep((long)(Math.random()*1000));
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		}
		
	}
}

다른 MusicPlayer와 MusicBoxExam클래스는 스레드와 공유 객체 부분에서 사용된 클래스와 동일

  • 해당 코드를 실행하면 신나는 음악, 힙합 음악 동시에 실행되다가 신나는 음악이 끝나면 슬픈 음악이 실행된다.
반응형