자바 공부/[자바] 기본 공부

[자바 공부 4일차] 쓰레드

햅2024 2024. 11. 11. 19:32

쓰레드는 다중 작업을 하기 위한 기능이다.

 

클래스 생성 시, 슈퍼클래스에서 Thread를 검색하여 상속 받는다.

 

Source -> Overridie/Implement Methods -> run()

 

 

 

Thread로 작업하고 싶은 함수를 작성한다.

package JavaUtil.exam;

public class MyThread1 extends Thread {

	String str;
	
	public MyThread1(String str)
	{
		this.str = str;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		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();
			}
		}
	}
}

 

 

해당 쓰레드를 상속 받아 함수를 실행한다.

package JavaUtil.exam;

public class ThreadExam {

	public static void main(String[] args) {
		MyThread1 t1 = new MyThread1("*");
		MyThread1 t2 = new MyThread1("-");

		t1.start();
		t2.start();
		
				
	}

}

 

-
*
*
*
-
*
*
*
-
-
*
*
-
-
*
-
-
*
-
-

두 함수가 각각 버벅거리지 않고 실행되는 걸 볼 수 있다.

 

 

  • Runnable로 생성하는 쓰레드

쓰레드 생성 시, Superclass로 상속받지 않고 interface에서 Runnable 쓰레드를 상속 받는다.

Java의 경우, 단일 상속만을 지원하기 때문에 이미 다른 클래스를 상속 받는 경우, 쓰레드를 클래스로 상속 받을 수 없다.

따라서 다른 클래스를 상속 받는 경우에 interface로 쓰레드를 상속 받아야 한다.

 

package JavaUtil.exam;

public class MyThread2 implements Runnable {
	String str;
	public MyThread2(String str) {
		this.str = str;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<10;i++) {
			System.out.println(str);
			
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}

 

 

쓰레드 출력

Runnable Thread는 Start라는 메소드가 없다. 따라서 Thread 생성자를 만들어서 넣어주어야 한다.

MyThread2 t2 = new MyThread2("-");
Thread t = new Thread(t2);
t.start();

 

  • 쓰레드와 공유객체
public class MusicBox {
	public void playMusicA()
	{
		for(int i=0;i<10;i++) {
			System.out.println("신나는 음악");
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public void playMusicB()
	{
		for(int i=0;i<10;i++) {
			System.out.println("슬픈 음악");
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public void playMusicC()
	{
		for(int i=0;i<10;i++) {
			System.out.println("카페 음악");
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}

 

 

package JavaUtil.exam;

public class MusicPlayer extends Thread {
	int type;
	MusicBox musicBox;
	
	public MusicPlayer(int type, MusicBox musicBox)
	{
		this.type = type;
		this.musicBox = musicBox;
	}

	@Override
	public void run() 
	{
		switch(type){
		case 1:
			musicBox.playMusicA();
			break;
			
		case 2:
			musicBox.playMusicB();
			break;
			
		case 3:
			musicBox.playMusicC();
			break;
		}
	}
	
	
}
package JavaUtil.exam;

public class MusicBoxExam {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		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 를 선언해준다.

 

package JavaUtil.exam;

public class MusicBox {
	public synchronized void playMusicA()
	{
		for(int i=0;i<10;i++) {
			System.out.println("신나는 음악");
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public synchronized void playMusicB()
	{
		for(int i=0;i<10;i++) {
			System.out.println("슬픈 음악");
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public synchronized void playMusicC()
	{
		for(int i=0;i<10;i++) {
			System.out.println("카페 음악");
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}

 

싱크로나이즈드를 사용할 경우

슬픈 음악
슬픈 음악
슬픈 음악
슬픈 음악
슬픈 음악
슬픈 음악
슬픈 음악
슬픈 음악
슬픈 음악
슬픈 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악
카페 음악
카페 음악
카페 음악
카페 음악
카페 음악
카페 음악
카페 음악
카페 음악
카페 음악
카페 음악

다음과 같이 한 쓰레드가 끝난 후에야 다음 쓰레드가 실행이 된다. 

 

이 중 특정 함수에 synchronized 를 뺀다면, 그 함수만 동시에 실행이 될 것이다.

package JavaUtil.exam;

public class MusicBox {
	public synchronized void playMusicA()
	{
		for(int i=0;i<10;i++) {
			System.out.println("신나는 음악");
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public synchronized void playMusicB()
	{
		for(int i=0;i<10;i++) {
			System.out.println("슬픈 음악");
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public void playMusicC()
	{
		for(int i=0;i<10;i++) {
			System.out.println("카페 음악");
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}
슬픈 음악
카페 음악
카페 음악
슬픈 음악
슬픈 음악
카페 음악
카페 음악
슬픈 음악
카페 음악
슬픈 음악
슬픈 음악
카페 음악
슬픈 음악
카페 음악
슬픈 음악
카페 음악
카페 음악
슬픈 음악
카페 음악
슬픈 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악
신나는 음악

 

 

그러나 함수 전체에 synchronized를 사용할 경우, 마지막에 실행되는 함수는 너무 오래 기다려야 될 것이다.

이를 해결하기 위해 동시에 실행되면 안되는 부분에만 synchronized를 선언해줄 수 있다.

이럴 경우, 쓰레드 자체는 동시에 실행되지만, 앞선 쓰레드의 작업이 완료되어야 싱크로나이즈드 한 부분이 실행된다.

public void playMusicB()
	{
		for(int i=0;i<10;i++) {
			synchronized(this)
			{
				System.out.println("슬픈 음악");
			}
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}