Dev/Java

[java] 스레드

창문닦이 2019. 1. 28. 19:13

Thread(스레드) : 자바로 멀티태스킹 하는 것을 가능하게 만드는 객체

스레드 => 프로세스 내부에서 실행되는 명령 모임

CPU가 지시해서 스레드 하나하나마다 작업을 시킴. 스레드들이 작업을 할땐 절대 중복되지 않음

V3 바이러스검사시(이게 하나의 프로세스) 섹터를 한번에 쭉 검사를 하게되면 너무 오래걸림.

논리적으로 섹터를 구분하여 동시에 검사함. -> 이때 사용되는 개념이 스레드

다중스레드 동시에 명령이 실행가능. 스레드마다 번갈아서 빠르게 실행되서 동시실행으로 보임

클래스는 다중상속이 안됨. ClassA extends ClassB,Thread <- 불가능! 이럴땐 Runnable 인터페이스 이용

스레드가 움직이는 기본적인 구조

class MyThread extends Thread {// Thread클래스 상속받아서 사용 가능

private int num;

private String name;

public MyThread(int num, String name) {

this.num = num;

this.name = name;

}

@Override

public void run() {// Thread클래스. 다양한 메소드 존재. 가장 중요한 건 run!. 스레드 사용시 반드시 런을 정의해줘야 함

int i = 0;

while (i < num) {

System.out.println(this.getName() + ":" + name + i);

i++;

// run() 안에는 무조건 try안에 sleep이 있어야 함!

try {

sleep(100); // 100-> 0.1초, 1000->1초. 메소드가 0.1초 만큼 쉬는 시간을 줌

} catch (Exception e) {

// TODO: handle exception

}

}

}

}

public class Test1 {//t1과 t2는 별개의 프로그램

public static void main(String[] args) {

System.out.println("main 시작......");

MyThread t1 = new MyThread(100, "첫번째 : "); //t1이 쉬는 sleep 0.1초 찰나의 시간에 t2가 올라감. 그래서 전체적인 흐름이 번갈아서 출력이 되는 것

MyThread t2 = new MyThread(200, "두번째 : ");

t1.start();//스레드호출메소드(run()메소드)

t2.start();

//스레드 3개가 실행된 것 (main, t1, t2)

System.out.println("main 종료......");

}

}



Runnable 인터페이스를 이용한 스레드

class MyThread2 implements Runnable {

private int num;

private String name;

public MyThread2(int num, String name) {

this.num = num;

this.name = name;

}

@Override

public void run() {

int i = 0;

while (i < num) {

System.out.println(name + ":" + i);

i++;

try {

Thread.sleep(100);

} catch (Exception e) {

// TODO: handle exception

}

}

}

}

public class Test2 {

public static void main(String[] args) {

System.out.println("main 시작....");

Thread t1 = new Thread(new MyThread2(100, "첫번째"));

Thread t2 = new Thread(new MyThread2(200, "두번째"));

//실행순서는 CPU가 조정하여 정확하게 일치시킬 순 없음

t1.start();

t2.start();

//스레드 3개가 실행된 것 (main, t1, t2)

System.out.println("main 종료....");//메인스레드가 종료되도 t1,t2는 계속 진행

}

}



캘린더를 활용하여 스레드 사용하기

import java.util.Calendar;

class TClock implements Runnable {

private int num;

public TClock(int num) {

this.num = num;

}

@Override

public void run() {

int i = 0;

while (i < num) {

System.out.printf("%1$tF %1$tT\n", Calendar.getInstance());//동일한 매개변수 사용시 1$

i++;

try {

Thread.sleep(1000);

} catch (Exception e) {

// TODO: handle exception

}

}

/*

//무한루프로 돌려도 상관없음

while(true){

System.out.printf("%1$tF %1$tT\n", Calendar.getInstance());

try {

Thread.sleep(1000);

} catch (Exception e) {

// TODO: handle exception

}

}

*/

}

}

public class Test3 {

public static void main(String[] args) {//main 스레드

System.out.println("main 시작....");

Thread t1 = new Thread(new TClock(100));

t1.start();

}

}



스레드 우선순위 : 1이 가장 낮고 10이 가장 높다

class MyThread4 extends Thread{

private String name;

public MyThread4(String name){

this.name = name;

}

@Override

public void run() {

for(int i=1;i<=20;i++){

System.out.println(name + ":" + i);

}

}

}

public class Test4 {

public static void main(String[] args) {

MyThread4 ob1 = new MyThread4("A");

MyThread4 ob2 = new MyThread4("B");

MyThread4 ob3 = new MyThread4("C");

//우선순위 종류(상수)

System.out.println("MIN: "+ Thread.MIN_PRIORITY); //1

System.out.println("NORM: "+ Thread.NORM_PRIORITY); //5

System.out.println("MAX: "+ Thread.MAX_PRIORITY); //10

//스레드 기본 우선순위 : 모든 스레드는 기본 우선순위를 5로 가짐

System.out.println(ob1.getPriority());//5

System.out.println(ob2.getPriority());//5

System.out.println(ob3.getPriority());//5

//우선순위를 변경

ob1.setPriority(Thread.MIN_PRIORITY);//1.  바로 숫자 2를 입력해도 됨

ob2.setPriority(Thread.NORM_PRIORITY);//5.

ob3.setPriority(Thread.MAX_PRIORITY);//10.

ob1.start();

ob2.start();

ob3.start();

}

}



Daemon 스레드

다른스레드에 도움을 주는 스레드로 다른 스레드가 종료되면 데몬스레드가 종료되지 않아도 프로세스가 종료된다.

class MyThread5 implements Runnable{

@Override

public void run() {

for(int i=1;i<=20;i++){

System.out.println(i);

try {

Thread.sleep(1000);

} catch (Exception e) {

// TODO: handle exception

}

}

}

}

public class Test5 {

public static void main(String[] args) {

System.out.println("main 시작...");

Thread t1 = new Thread(new MyThread5());

Thread t2 = new Thread(new MyThread5());

Thread t3 = new Thread(new MyThread5());

//데몬스레드는 main이 살아있는 만큼만 run함. 원래 스레드는 메인스레드가 종료되도 계속 실행됨.

t1.setDaemon(true);

t2.setDaemon(true);

t3.setDaemon(true);

t1.start();

t2.start();

t3.start();

try {

Thread.sleep(1000);//메인이 2초 쉼

} catch (Exception e) {

// TODO: handle exception

}

try {

t1.join();//main에게 t1이 종료할 때까지 기다리라는 명령어. Waits for this thread to die.

t2.join();

t3.join();

} catch (Exception e) {

// TODO: handle exception

}

System.out.println("main 종료...");//join이 없을때, main스레드가 끝나면 데몬스레드도 같이 종료

}

}



스레드 생명주기(Time to live)

class MyThread6 extends Thread{

@Override

public void run() {

try {

System.out.println("스레드 시작..");

System.out.println("우선순위: " + getPriority());

System.out.println("스레드 이름: " + getName());//첫번째 만들어지는 스레드의 내부적인 이름 : Thread-0

sleep(500);//0.5초 쉼

//우선순위 변경

setPriority(2);

System.out.println("변경된 우선순위: " + getPriority());

System.out.println("스레드 종료..");

} catch (Exception e) {

// TODO: handle exception

}

}

}

public class Test6 {

public static void main(String[] args){

Thread t1 = Thread.currentThread();//현재 스레드(=main 스레드)를 t1에 할당.

Thread t2 = new MyThread6();//upcast. MyThread6가 가지고 있는 메소드중 부모클래스가 없는게 있다면 upcast 안됨

System.out.println("main스레드 우선순위: " + t1.getPriority());//모든 스레드는 디폴트가 5

System.out.println("main스레드 이름: "+ t1.getName());//main

System.out.println("start()메소드 호출 전의 isAlive: " + t2.isAlive());//start이전이므로 당연히 false

t2.start();

//t2의 우선순위

System.out.println("t2의 우선순위: " + t2.getPriority());

//t2의 우선순위 변경(5->1)

t2.setPriority(1);

try {

//0.1초 쉼

Thread.sleep(100);

//t2종료확인

System.out.println("t2 살아있냐? " + t2.isAlive());

//1초 쉼

Thread.sleep(1000);

//t2종료확인

System.out.println("1초후 t2 살아있냐? " + t2.isAlive());

t2.join();//main스레드 wait요청. 이미 t2가 종료되어있으므로 join해도 종료되어있음. 한번 종료된 스레드는 다시 살릴 수 없음

System.out.println("t2 그래도 살아있냐? " + t2.isAlive());

} catch (Exception e) {

// TODO: handle exception

}

}

}



인터럽트

우선순위가 높은 프로그램을 먼저 실행시키고 다시 돌아온다. 꼬리를 물면서 실행시키는 방법.

class MyThread7 extends Thread{

private Thread next;

public void setNext(Thread next){

this.next=next;

}

@Override

public void run() {

for(int i=1;i<=20;i++){

try {

sleep(2000);//2초쉼

} catch (Exception e) {

// TODO: handle exception

}

System.out.println(getName()+":"+i);

if(next.isAlive())//스레드가 살아있으면 멈춰라

next.interrupt();//다음번 스레드를 살려내는 역할을 하는게 인터럽트

}

}

}

public class Test7 {

public static void main(String[] args) {

MyThread7 t1 = new MyThread7();

MyThread7 t2 = new MyThread7();

MyThread7 t3 = new MyThread7();

t1.setNext(t2);//t1이 멈추면 t2이 살아남

t2.setNext(t3);//t2이 멈추면 t3이 살아남

t3.setNext(t1);//t3이 멈추면 t1이 살아남

t1.start();

t2.start();

t3.start();

t1.interrupt();

}

}



동기화 블록

class MyThread8 implements Runnable{

private int bank = 10000;

private int getBank(){//잔액확인메소드

return bank;

}

private int drawMoney(int m){//잔액인출메소드

bank -=m;//bank=bank-m;

return m;

}

@Override

public void run() {

int money_need = 6000;//인출금액

int money;//잔액

String msg = "";

try {

//동기화블럭 t1 스레드가 들어오게되면 다른 스레드 절대 접근불가

synchronized (this) {

if (getBank() > money_need) {//잔고가 더 크면 진행

Thread.yield();

// A hint to the scheduler that the current thread is willing to yield its current use of a processor.

// The scheduler is free to ignore this hint.

// sleep 메소드처럼 다른 스레드에게 실행할 기회를 주기 위해 사용됨.

// 우선권이 동일한 스레드에게 실행 기회 제공

money = drawMoney(money_need);

} else {

money = 0;

msg = ", 인출실패";

}

}

System.out.println(Thread.currentThread().getName()+ msg +", 인출금액: " + money + ", 잔고: "+ getBank());

} catch (Exception e) {

// TODO: handle exception

}

}

}

public class Test8 {

public static void main(String[] args) {

MyThread8 ob = new MyThread8();

//Thread t1 = new Thread(new MyThread8()); 동일

Thread t1 = new Thread(ob);

Thread t2 = new Thread(ob);

t1.start();

t2.start();

//스레드 t1,t2가 run을 점유하려고 진행하다보니 잔고가 -2000, -2000이 되는 경우 발생

//동기화 블록으로 해결 synchronized(){}

//wait() 메소드,  notify() 메소드 는 동기화 블록에서만 사용

}

}



wait() 메소드,  notify() 메소드 는 동기화 블록 synchronized(){} 에서만 사용

class MyThread9 implements Runnable{

private int bank = 10000;

private int getBank(){

return bank;

}

private int drawMoney(int m){

if(getBank()>0){

bank -= m ;

System.out.println(Thread.currentThread().getName() + ",인출: " +m +", 잔액:" + bank);

}else{

m=0;

System.out.println(Thread.currentThread().getName() + "잔액부족!");

}

return m;

}

@Override

public void run() {

synchronized (this) {// 클래스 자체가 들어와야함. 클래스 자체가 스레드.

for (int i = 0; i <= 10; i++) {

if (getBank() <= 0) {

this.notifyAll();// 대기상태의 모든스레드를 시작

break;

}

drawMoney(1000);

if (getBank() == 2000 || getBank() == 4000 || getBank() == 6000|| getBank() == 8000) {

try {

wait();//stop

// synchronized은 원래 다른 스레드가 들어올 수 없지만 wait가 있으면 가능.

// 하나의 스레드가 사용중이면 다른 스레드는 동기화 블럭에 들어올 수 없지만 wait()가 있으면 가능하다.

} catch (Exception e) {

}

} else {

notify();

}

}

}

}

}

public class Test9 {

public static void main(String[] args) {

MyThread9 ob = new MyThread9();

Thread t1 = new Thread(ob);

Thread t2 = new Thread(ob);

t1.start();

t2.start();

}

}



정해진 시간마다 특정 작업을 하고 싶을 때

import java.util.Calendar;

import java.util.Timer;

import java.util.TimerTask;

public class Test10 extends Thread{

private int num = 10;

public Test10(){

TimerTask task = new TimerTask() {//익명의클래스

@Override

public void run() {

//반복 실행할 작업

num = 0;

}

};

Timer t = new Timer();

Calendar d = Calendar.getInstance();

/*

 매일 0시 0분 0초부터 하루에 한번씩 반복

 d.add(Calendar.Date,1);

 d.set(Calendar.Hour,0); //오후 1시는 13으로 표현. 0은 밤 12시. 1은 새벽 1시

 d.set(Calendar.MINUTE,0); //분

 d.set(Calendar.SECOND,0); //초

 d.set(Calendar.MILLISECOND,0); //밀리세컨

 t.schedule(task, d.getTime(),1000*60*60*24)//밀리세컨*초*분*시

*/

t.schedule(task, d.getTime(), 5000);// 5000:interval(5초간격)으로 실행

}

@Override

public void run() {

while(true){

System.out.println(num++);

try {

sleep(1000);

} catch (Exception e) {

// TODO: handle exception

}

}

}

public static void main(String[] args) {

//Test10 ob = new Test10();

//ob.start();

new Test10().start();//객체를 만들어서 한번만 실행하고 끝나면 됨. 메모리의 낭비를 줄일 수 있음.

}

}



스레드 그룹

스레드를 여러개 만들어서 사용할 때 그룹지어 사용

public class Test11 {

public static void main(String[] args) {

System.out.println("메인 스레드 그룹 : " +Thread.currentThread().getThreadGroup());//main이라는 그룹이 있는데 그안에 스레드가 있는 것.

System.out.println("메인 : " + Thread.currentThread());//메인스레드그룹이 들어있는 메인스레드의 정보..?

Thread t1 = new Thread();

System.out.println("t1 스레드 그룹 : " +Thread.currentThread().getThreadGroup());//t1스레드를 만들면 항상 메인스레드 그룹에 들어감

System.out.println("t1 : " +t1);

//Thread[Thread-0,5,main] = 스레드이름 Thread-0, 우선순위 5, main 스레드그룹에 들어있다

ThreadGroup tg = new ThreadGroup("tg");//그룹이름

Thread t2 = new Thread(tg, "t2");

Thread t3 = new Thread(tg, "t3");

System.out.println("t2 : " +t2);

System.out.println("t3 : " +t3);

}

}


랜덤으로 발표자 선정하기 + 스레드 활용하여 발표 결과 출력

import java.util.Random;

import java.util.Scanner;

class MyThread12 extends Thread {

@Override

public void run() {

for(int i=0;i<15;i++){

System.out.print(".");

try {

sleep(200);

} catch (Exception e) {

System.out.println(e.toString());

}

}

}

}

public class Test12 {

public static void main(String[] args) throws InterruptedException {

Scanner sc = new Scanner(System.in);

Random rd = new Random();

MyThread12 t12 = new MyThread12();

String name[] = {"오길동", "이길동", "정길동", "박길동", "남길동", "김길동",

"강길동", "정길동", "전길동", "나길동", "장길동", "간길동", "최길동", "황길동"};

String presenter[];//발표자 몇명일지 모름. 객체 생성만.

int num, su, checkName;

do{

System.out.print("발표자 인원수를 입력해주세요[1~18]:");

num = sc.nextInt();

}while(num<1 || num>=18);

presenter = new String[num];//배열크기 지정

su=0;

while(su<num){

checkName = rd.nextInt(18);

presenter[su] = name[checkName];

for(int i=0;i<su;i++){

if(presenter[i].equals(presenter[su])){

su--;

break;

}

}

su++;

}

System.out.print("고민중");

t12.start();

try {

t12.join();//main절이 끝나지 않도록 기다려라.

} catch (Exception e) {

System.out.println(e.toString());

}

System.out.println();

System.out.println("\n***축하합니다! 발표자 입니다***");

System.out.println("*                             *");

int k=1;

for(String i:presenter){

System.out.printf("*%6d번 발표자 : %s     *\n" ,k, i);

k++;

Thread.sleep(50);

}

System.out.println("*                             *");

System.out.println("*******************************");

sc.close();

}

}