[JAVA] Thread / SingleThread / MultiThread / ThreadSynchronized

1
post-thumbnail

Thread 란 ?


  • 메모리를 할당받아 실행 중인 프로그램을 프로세스라고 합니다.
  • 프로세스 내의 명령어 블록으로 시작점과 종료점을 가진다.
  • 실행중에 멈출 수 있으며 동시에 수행 가능하다.
  • 어떠한 프로그램내에서 특히 프로세스 내에서 실행되는 흐름의 단위.

Thread 생성

  • 2가지 방법
    ① 직접 상속 받아 스레드 생성
    ② Runnable 인터페이스를 구현해서 생성
  • Thread 클래스 이용
  • Thread 클래스로 부터 제공되는 run()메소드 오버라이딩해서 사용
  • Ex)
class ThreadA extends Thread {
public void run() {
  // 수행할 문장들 기술
	}
}
  • 실제 사용
ThradA TA = new ThreadA();
TA.start();

<예제 소스코드1 - ThreadTest.class>

public class ThreadTest extends Thread
{
    public void run()
    {
        // 인터럽트 됬을때 예외처리
        try
        {
            for(int i = 0 ; i < 10 ; i++)
            {
                // 스레드 0.5초동안 대기
                Thread.sleep(500);
                System.out.println("Thread : " + i);
            }
        }catch(InterruptedException e)
        {
            System.out.println(e);
        }
    }
}

<예제 소스코드2 - Thread1.class>

public class Thread1 
{
    public static void main(String args[])
    {
        ThreadTest t1 = new ThreadTest();
        ThreadTest t2 = new ThreadTest();
        
        // 1. 동시에 똑같은 숫자가 나오고(start)
        /*t1.start();
        t2.start();*/
        
        // 2. 번갈아가면서 나옴(run)
        t1.run();
        t2.run();
    }
}
  • 결과1. start() 동시에 똑같은 숫자가 나옴
  • 결과2. run() 번갈아가면서 나옴

단일스레드 예제


  • S_thread.java
package study;

//단일스레드
public class S_thread {
	public void display() {
		for(char i = 'A'; i <= 'Z'; i++) {
			System.out.print(i);
		}
		System.out.println();
	}
	public static void main(String[] args) {
		System.out.println("Main 스레드 시작 !!!");
		
		new S_thread().display();
		
		for(char i = 'a'; i <= 'z'; i++) {
			System.out.print(i);
		}
		System.out.println();
		System.out.println("Main 스레드 끝!!!");
	}
}
  • 결과

멀티스레드 예제


  • M_thread.java
package study;

public class M_thread extends Thread {
	@Override
	public void run() {
		for(char i = 'A'; i<= 'Z'; i++) {
			System.out.print(i);
			try {
				Thread.sleep(500);
			}catch(InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println();
	}
}
  • M2_thread.java
package study;

public class M2_thread implements Runnable{
	
	@Override
	public void run() {
		for(char i = 'a'; i <= 'z'; i++) {
			System.out.print(i);
			try {
				Thread.sleep(500);
			}catch(InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
  • MultiThread
package study;

public class MultiThread {
	public static void main(String[] args) throws InterruptedException {
    
		//M_thread m = new M_thread();
		//m.start();
		new M_thread().start();
		
		//M2_thread m2 = new M2_thread();
		//Thread thread = new Thread(m2);
		//thread.start();
		new Thread(new M2_thread()).start();
		
		for(int i = 0; i<= 9; i++) {
			System.out.print(i);
			Thread.sleep(500); // 0.5초동안 스레드의 흐름을 일시정지
		}
	}
}

Thread_Synchronized 예제


package study;

public class ThreadSynchronizedTest {
	
	public static void main(String[] args) { 
		
		Task task = new Task();
		
		Thread t1 = new Thread(task);
		Thread t2 = new Thread(task);
		t1.setName("t1-Thread");
		t2.setName("t2-Thread");
		
		t1.start();
		t2.start();
	}
}

class Account {
	int balance = 1000;
	
	public synchronized void withDraw(int money) {
		
		if(balance >= money) {
			try {
				Thread thread = Thread.currentThread(); //현재 쓰레드
				System.out.println(thread.getName() + " 출금 금액 ->> " + money);
				Thread.sleep(1000);
				balance -= money; //balance = balance - money
				System.out.println(thread.getName()+ " balance : " + balance);
			}catch(Exception e) {
			}
		}
	}
}

class Task implements Runnable {
	
	Account acc = new Account();
	
	@Override
	public void run() {
		
		while(acc.balance > 0) {
			
			// 100, 200, 300 중의 한 값을 임의로 선택해서 출금(withDraw)한다.
			int money = (int)(Math.random() * 3 + 1)* 100;
			
			acc.withDraw(money);
		}
	}
}

위 예제에 대해 설명을 하면, Account라는 클래스에는 balance 잔액과 이 잔액을 삭감(withDraw)시키는 인출메서드가 있습니다. 스레드를 만들 때 Runnable 인터페이스를 구현하여 Task 클래스를 만들고 이 Task 클래스를 Thread 객체 생성시 생성자에 매개변수로 넣으면 만든 스레드가 Task에 정의되어 있는대로 일을 실행합니다.
Runnable을 구현하여 만든 Task에 100, 200, 300 중 랜덤하게 값을 전달받아 money 변수에 할당하고 그 금액만큼 Account 인스턴스의 인출메서드를 호출해 balance (잔액)을 출금시키는 일을 구현해놨습니다. 다음 main 메서드에서 스레드 t1, t2를 만들고 각각의 스레드 이름을 정의합니다. t1, t2 스레드가 시작하면 잔액이 0이 될 때까지 두 스레드가 경쟁하며 출금시킬 것입니다.

  • 결과 화면

    여기서 멀티스레드의 문제점이 발견됩니다. balance thread-safe가 되지 않았기 때문에 t1 스레드가 잔액을 삭감하기 전에 t2가 balance에 접근해 삭감을 해버리고 다시 t1이 sleep()에서 깨어나 balance을 삭감해버리기 때문에 잔액이 0 이하의 마이너스 값을 가지게 됩니다.
    해결하는 방법은 -> synchronized키워드를 리턴타입 앞에 붙여주면 됩니다. t1 스레드가 먼저 공유데이터나 메서드에 점유하고 있는 상태인 경우 block으로 처리하기 때문에 t1 이외의 스레드의 접근을 막습니다. t1 스레드가 작업을 다 끝내면 .unblock으로 처리하여 t1 이외의 스레드의 접근을 허락합니다.

synchronized 키워드로 멀티스레드 동기화 처리


한 가지가 바뀐 것을 볼 수 있다.

package study;

public class ThreadSynchronizedTest {
	
	public static void main(String[] args) { 
		
		Task task = new Task();
		
		Thread t1 = new Thread(task);
		Thread t2 = new Thread(task);
		t1.setName("t1-Thread");
		t2.setName("t2-Thread");
		
		t1.start();
		t2.start();
	}
}

class Account {
	int balance = 1000;
	
	public synchronized void withDraw(int money) {
		
		if(balance >= money) {
			try {
				Thread thread = Thread.currentThread(); //현재 쓰레드
				System.out.println(thread.getName() + " 출금 금액 ->> " + money);
				Thread.sleep(1000);
				balance -= money; //balance = balance - money
				System.out.println(thread.getName()+ " balance : " + balance);
			}catch(Exception e) {
			}
		}
	}
}

class Task implements Runnable {
	
	Account acc = new Account();
	
	@Override
	public void run() {
		
		while(acc.balance > 0) {
			
			// 100, 200, 300 중의 한 값을 임의로 선택해서 출금(withDraw)한다.
			int money = (int)(Math.random() * 3 + 1)* 100;
			
			acc.withDraw(money);
		}
	}
}
  • 결과 화면

synchronized 키워드를 사용함으로써 balance 공유데이터에 대한 thread-safe를 시켰기 때문에
데이터나 메서드 점유하고 있는 스레드가 온전히 자신의 작업을 마칠 수 있습니다.

참고: https://coding-start.tistory.com/68 [코딩스타트]

profile
ᴅᴇ́ᴠᴇʟᴏᴘᴘᴇᴜʀ. ᴘʀᴏɢʀᴀᴍᴍᴀᴛɪᴏɴ 🔥

2개의 댓글

comment-user-thumbnail
2021년 5월 27일

와우... 감사합니다.

1개의 답글