자바 언어에는 동시에 여러 일을 처리하도록 하는 기능(비동기적)이 있다. 스레드 개념이 그것인데, main 코드 안에 스레드를 여러개 만들어서 동시에 여러 일을 처리하게 할수 있다. 스레드 역시 객체로 만들어서 이용해야 하는데 이때 Thread 클래스 또는 Runnable 인터페이스를 이용합니다. 이번 장에서는 스레드 개념에 대해서 알아보고, 스레드를 이용한 코딩을 어떻게 하는지 알아보자
멀티태스킹은 이름 그대로 여러 가지 일을 동시에 처리하는 것을 말하며, Thread 클래스 또는 Runnable 인터페이스를 이용해서 멀티태스킹을 하는 문법이 어느 정도는 정해져있다. 예제들을 직접 수행해보면서 문법과 코드 구현방식을 익혀보자
지금까지는 main 메소드가 하나의 일을 처리했고 스레드를 이용하면 main메소드 안에서 여러가지 일을 동시에 처리할수 있다.
컴퓨터에서 여러가지 일을 동시에 처리하는 것을 멀티태스킹이라고 하며, 멀티태스킹은 크게 프로세스 기반의 멀티태스킹과 스레드 기반의 멀티태스킹이 있다.
현재 수행되고 있는 프로그램을 프로세스 라고 하며, 스레드는 작은 프로세스라고 생각해도 된다. 그리고 main함수를 main 스레드라고 한다.
스레드 기반의 멀티태스킹을 중점으로 알아보자, 즉 main메소드 내에서 여러개의 스레드를 생성하여 일을 동시에 수행시키는 방법이다.
메인 메소드 내에서 스레드 객체를 만들어 수행시키면서 동시에 스레드가 다른 일을 처리할 수 있도록 코드를 구현할 수 가 있다. Thread 객체는 Treadh 클래스 또는 Runnable 인터페이스를 통해서 만들 수가 있는데, 우선 Thread클래스를 이용해 스레드를 만드는 방법을 알아보자.
[Thread 생성자]
| 생성자 | 설명 |
|---|---|
| Thread() | 스레드 객체를 생성 |
| Thread(Runnable target) | Runnable 인터페이스를 이용하여 스레드 객체 생성 |
| Thread(Runnable target, String name | Runnable 인터페이스를 이용하여 이름있는 스레드 객체를 생성 |
| Thread(String name) | 이름있는 스레드 객체를 생성 |
[Thread 메소드]
| 메소드 | 설명 |
|---|---|
| String getName() | 스레드 이름을 반환 |
| int getPriority | 스레드의 우선순위를 반환함 |
| boolean isAlive() | 스레드가 아직 수행 중인지 판단함 |
| void join() | 스레드가 끝나기를 기다림 |
| void run() | 스레드가 Runnable run 객체로부터 생성되었다면 Runnable 객체의 run 메소드가 호출됨 |
| void setName(String name) | 스레드에 이름을 붙임 |
| void setPriority(int newPriority) | 스레드에 우선순위를 주는 메소드 |
| static void sleep(long miles) | 현재 수행 중인 스레드를 milis 밀리세컨드 동안 중지 |
| void start() | 스레드 수행을 시작하게 함. |
스레드를 만드려면 Thread 클래스를 상속받는 클래스를 만들어서 그 클래스 안에서 run()메소드를 오버라이딩 해야한다 run()메소드는 스레드가 수행하는 코드이다
run() 메소드를 수행하려면 스레드 객체를 생성한 후에 start()라고 호출해야 한다. 즉 스레드, start()라고 해야 스레드 클래스의 run() 메소드가 자동 호출된다.
class MyThread extends Thread{ //스레드 클래스를 상속
public void run(){ //Thread 클래스의 run()메소드 오버라이딩
...
}
}
public class ThreadTest{ // MyThread 객체를 만들어서 수행할 클래스
public static void main(String[] args){
Thread t = new MyThread(); // MyThread 객체 생성
t.start(); //t.start()를 해서 run()메소드 가 호출
}
}
스레드는 어떻게 수행되는가?
[예제]
package thread;
class NumberThread extends Thread{
public void run(){
for(int i = 1 ; i<=26; i++){
System.out.print(i);
}
}
}
public class Test1 {
public static void main(String[] args)
{
Thread t = new NumberThread( );
t.start( );//t스레드
for (char ch = 'A'; ch <= 'Z'; ch++)//메인스레드
System.out.print(ch);
}
}
3번 실행시 출력이 다르다
mian 메소드가 수행되면서 t객체를 생성해 수행하면 스레드 2개가 동시에 수행되면서 main메소드와 run()메소드가 동시에 수행되는 것이다 하지만 cpu는 매순간 한가지의 일만하기 때문에 교차해서 수행하는것이다 그래서 섞여잇는것
수행결과를 조금 천천히 보고 싶다면 sleep()메소드를 이용하자 인수는 밀리세컨드이며 1000 === 1초 랑 같다
package thread;
class NumberThread extends Thread{
public void run(){
for(int i = 1 ; i<=26; i++){
System.out.print(i);
try{
Thread.sleep(100);
}catch(InterruptedException e){
System.out.println(e.getMessage());
}
}
}
}
public class Test1 {
public static void main(String[] args)
{
Thread t = new NumberThread( );
t.start( );//t스레드
for (char ch = 'A'; ch <= 'Z'; ch++){//메인스레드
System.out.print(ch);
}
try{
Thread.sleep(100);
}catch(InterruptedException e){
System.out.println(e.getMessage( ));
}
}
}
타이핑하듯이 수행 결관는 다름
두 개의 스레드 객체를 수행하는 예제
[예제]
package thread;
class NumberThread extends Thread{
public void run(){
for(int i = 1 ; i<=26; i++){
System.out.print(i);
try{
Thread.sleep(100);
}catch(InterruptedException e){
System.out.println(e.getMessage());
}
}
}
}
class CharThread extends Thread{
public void run(){
for (char ch = 'A'; ch <= 'Z'; ch++){//메인스레드
System.out.print(ch);
}
try{
Thread.sleep(100);
}catch(InterruptedException e){
System.out.println(e.getMessage( ));
}
}
}
public class Test1 {
public static void main(String[] args)
{
Thread t1 = new NumberThread( );
Thread t2 = new CharThread();
t1.start();//t스레드
t2.start();
for(char ch = 'A'; ch<='Z';ch++){
System.out.print(ch);
}try{
Thread.sleep(100);
}catch(InterruptedException e){
System.out.println(e.getMessage());
}
}
}
결관느 약간식 다름
메인메소드와 스레드 2개를 동시에 수행 전부 섞여서 나올수있음
cpu가 프로세스 순서에따라 진행하는데 환경이 다를수 있음
스레드 생성자 중에서 Runnable 인터페이스를 이용해 스레드 객체를 생성하는게 있다. 다음 생성자를 이용해야한다.
Thread(Runnable target)
Runnable 인터페이스를 이용해 스레드를 만드려면 다음과같이 Runnable 인터페이스를 구현한 스레드 클래스를 만들어야 한다. 그리고 그 클래스 안에 run()메소드를 오버라이딩한다.
class MyThread implements Runnable{//Runnable 인터페이스를 구현하는 클래스
public void run(){
....
}
}
public class RunnableTest{
public static void main(String[] args){
Thread t = new Thread(new NumberThread());//스레드 객체 생성
t.start() //런 메소드 수행;
for(char ch = "A" ; ch<="Z"; ch++){
System.out.print(ch);
}
}
}
package thread.runnable;
class NumberThread implements Runnable{
public void run(){
for(int i = 1; i <= 26 ; i++){
System.out.print(i);
}
}
}
public class Test2 {
public static void main(String[] args){
Thread t = new Thread(new NumberThread());
t.start();
for(char ch = 'A' ; ch <= 'Z' ; ch++){
System.out.print(ch);
}
}
}
결과적으론 차이가 없다
Thread 클래스를 상속 받아서 만든 스레드 수행과 Runnable 인터페이스 구현을 이용한 스레드 수행 방법 모두 결과는 차이가 없다. 둘중의 편한방법을 쓰면 되지만 , Thread클래스를 상속받는 경우에는 다른 클래스의 상속이 불가능해서 Thread 클래스 외에 다른 클래스도 상속받아야 하는 경우라면 Runnable 인터페이스를 구현하도록 하자.
순서 Thread 난 Runnable 인터페이스를 상속받은후
메인에서 쓰레드 객체를 만들고 그 인수로 클래스를 집어 넣고 런 메소드를 실행시키기 위해 start()를 실행
스레드를 여러 개 생성후 동시에 수행하는 코드를 작성할때 스레드에 이름을 붙여서 사용하면 편리할수 있다. 스레드에 이름을 붙여 구별할수 있도록 알아보고 이름을 붙여서 동시에 수행해보도록 하자 먼저 이름이 있는 스레드를 만들어 보자.
Thread(String name)
Thread(Runnable target, String name)
스레드 클래스를 상속 받아 이름이 있는 스레드 객체를 생성해보자.
package thread.naming;
class NumberThread extends Thread{
String name;
NumberThread(String name){
this.name = name;
}
public void run(){
System.out.println(name + "starting");
try{
for(int i = 0; i< 10; i++){
Thread.sleep(100);
System.out.println("in" + name +":" + i);
}
}catch(InterruptedException e){
System.out.println(e);
}
System.out.println(name + "terminating");
}
}
public class Test {
public static void main(String[] args){
System.out.println("main starting");
Thread th = new Thread(new NumberThread("Number Thread"));
th.start();
for(int i = 0 ; i<10 ; i++){
try{
Thread.sleep(100);
System.out.println("In main : " + i);
}catch(InterruptedException e){
System.out.println(e);
}
}
System.out.println("main ending");
}
}
main starting
Number Threadstarting
inNumber Thread:0
In main : 0
inNumber Thread:1
In main : 1
inNumber Thread:2
In main : 2
inNumber Thread:3
In main : 3
In main : 4
inNumber Thread:4
In main : 5
inNumber Thread:5
In main : 6
inNumber Thread:6
In main : 7
inNumber Thread:7
In main : 8
inNumber Thread:8
inNumber Thread:9
In main : 9
main ending
Number Threadterminating
반복실행시 결과는 달라질수 있음
메인이 실행되는지 NumberThread 가 실행되는지 순서를 알수있게 적용
이번엔 스레드를 2개 만들어 main 스레드와 같이 세 개를 동시에 수행해 보도록 하자. 이때 하나의 스레드는 Thread 클래스를 상속 받고, 다른하나는 Runnable 인터페이스를 구현해보도록 하자
package thread.naming;
class NumberThread extends Thread{
String name;
NumberThread(String name){
this.name = name;
}
public void run(){
System.out.println(name + "starting");
try{
for(int i = 0 ; i<10 ; i++){
Thread.sleep(100);
System.out.println("IN" + name + ":" + i);
}
}catch(InterruptedException e){
System.out.println(e);
}
System.out.println(name + "terminating");
}
}
class CharThread implements Runnable{
String name;
CharThread(String name){
this.name = name;
}
public void run(){
System.out.println(name + "starting");
try{
for(char i = 'A' ; i<='J';i++){
Thread.sleep(100);
}
}catch(InterruptedException e){
System.out.println(e);
}
System.out.println(name + "terminating");
}
}
public class Test2 {
public static void main(String[] args){
System.out.println("main starting");
Thread th1 = new Thread(new NumberThread("Number Thread"));
Thread th2 = new Thread(new CharThread("Character Thread"));
th1.start();
th2.start();
for(int i = 0; i<10; i++){
try{
Thread.sleep(100);
System.out.println("In main : " + i);
}catch(InterruptedException e){
System.out.println(e);
}
}
System.out.println("main ending");
}
}
이것또한 방법이며 쓰임에 맞게 잘 써보면 될거 같다
main 스레드가 수행되면서 다른 일을 하는 스레드를 만들어 수행하는 예제들을 만들어 보았는데, 이때 main 스레드가 만든 스레드가 main보다 먼저 끝나기도 하고 나중에 끝나기도 했다. 즉, main이 만든 스레드라하더라도 main과 똑같이 경쟁하면서 cpu를 사용하기 때문에 어느 스레드가 먼저 수행을 끝낼지는 알수가 없다. 하지만 시스템 입장에서는 main스레드가 자기가 만든 스레드보다 나중에 끝나는게 좋다. 따라서 main 스레드는 자기가 만들어서 수행시킨 스레드가 모두 끝날 때까지 기다리는 것이 필요하다. 이럴경우 두메소드를 사용해서 스레드를 조절할 수 있다.
| 메소드 | 설명 |
|---|---|
| boolean isAlice() | 스레드가 아직 수행 중인지 판단함 |
| void join() | 스레드가 끝나기를 기다림 |
우선 아래 코드 수행결과를 보자
package thread.alivejoin;
class ChildThread extends Thread{
public void run(){
for(char ch = 'A'; ch<='Z';ch++){
System.out.print(ch);
}
}
}
public class Test {
public static void main(String[] args){
Thread th = new ChildThread();
th.start();
int i = 1;
do{
System.out.print("*");
i++;
}while(i<=26);
}
}
ABCDEFGHIJKLMNOPQRSTUVWXYZ**************************%
**************************ABCDEFGHIJKLMNOPQRSTUVWXYZ%
순서가 다르게 나올수있어서 isAlice()메소드를 사용하여 main 스레드가 항상 나중에 끝나도록 수정해보자
package thread.alivejoin;
class ChildThread extends Thread{
public void run(){
for(char ch = 'A'; ch<='Z';ch++){
System.out.print(ch);
}
}
}
public class Test {
public static void main(String[] args){
Thread th = new ChildThread();
th.start();
int i = 1;
do{
System.out.print("*");
i++;
}while(th.isAlive());
}
}
th가 살아있는동안 루프를 계속 수행하기떄문에 th가 끝나면 do~while이 끝난다 그래서 섞어서 실행이되다가 결국은 * 로 끝나게 된다
다음은 join()메소드를 이용하여 main스레드를 무조건 나중에 끝내보도록하자 join메소드는 스레드가 끝나기를 기다리는 메소드로 다음과같다.
public fianl void join() throws InterruptedException
join()메소드를 호출하는 스레드를 기다리는 것 예제를 보자
package thread.alivejoin;
class ChildThread extends Thread{
public void run(){
for(char ch = 'A'; ch<='Z';ch++){
System.out.print(ch);
}
}
}
public class Test {
public static void main(String[] args){
Thread th = new ChildThread();
th.start();
for(int i =1 ; i<=26; i++){
System.out.print(i);
}
try{
th.join();//th가 끝날떄까지 기다려야 해서 main메소드가 th메소드가 끝날때까지 기다림
}catch(InterruptedException ex){
System.out.println("main thread interrupted");
}
System.out.println("main thread ending");
}
}
join전까지는 섞어서 실행되다가 조인뒤에 나오는 main thread ending문구는 제일 나중에 실행
지금까지 스레드를 수행한 결괄르 보면 main 스레드가 수행시킨 스레드이지만 일단 수행이 시작되면 main 스레드이든, main 스레드가 수행시킨 스레드이든, 동일한 조건에서 스레드를 수행함을 알수 있다. 컴퓨터 입장에서는 main 스레드도 똑같이 스레드 중 하나라고 생각하고 모두 동일 조건에서 수행된다. 그래서 앞의 예제들을 보면 main스레드가 자기가 수행시킨 스레드보다 먼저끝나거나 나중에 끝나거나 섞어서끝나거나 더 나중에 끝나기도 한다. 모든 스레드는 기본적으로 우선순위가 같기 떄문에 생기는 현상이다. 만약에 여러 스레드 중에서 먼저 수행되어야 하는 중요한 스레드가 있다면 스레드에 우선순위를 주어서 먼저 수행할 수 있도록 조절할수 있다. 높은 우선순위를 갖는 스레드는 cpu를 오래 사용할수 있고, 낮은 우선 순위를 갖는 스레드는 오래사용할수 없다. 그래서 중요한 스레드라면 우선순위를 높여주는 것이 좋다.
스레드 클래스에는 다음과 같이 우선순위와 관련된 필드를 가지고있다.
| 필드 | 설명 |
|---|---|
| static int MAX_PRIORITY | 스레드가 가질 수 있는 최대 우선순위(10) |
| static int MIN_PRIORITY | 스레드가 가질 수 있는 최소 우선순위(1) |
| static int NORM_PRORITY | 스레드에게 주어지는 디폴트 우선순위(5) |
우선순위는 1부터 10까지의 값을 갖고 다음과 같이 우선순위 1, 5, 10은 정적 변수 MIN_PRORITY,NORM_PRIORTY,MAX_PRIORITY 에 저장되어 있다. 사용자가 만드는 스레드는 NORM_PRIORITY가 디폴트이기 때문에 우선순위 5를 갖게 됩니다. 다음의 예를 보면 PRiority 스레드의 우선순위를 출력했을 때 5가 출력되는 것을 알수있다.
package thread.priority;
class Priority extends Thread{
String name;
Priority(String name){
this.name = name;
}
public void run(){
System.out.println(name + "String");
try{
for(int i = 0; i<5; i++){
Thread.sleep(300);
System.out.println("In" + name + ":" + i +"(priority: " + getPriority() +")");
}
}catch(InterruptedException e){
System.out.println(e);
}
System.out.println(name + "terminating");
}
}
public class Test {
public static void main(String[] args){
System.out.println("main starting");
Thread th = new Thread(new Priority("Priority Thread"));
th.start();
for(int i = 0; i<5; i++){
try{
Thread.sleep(300);
System.out.println("In main: " + i);
}catch(InterruptedException e){
System.out.println(e);
}
}
System.out.println("main ending");
}
}
main starting
Priority ThreadString
In main: 0
InPriority Thread:0(priority: 5)
InPriority Thread:1(priority: 5)
In main: 1
In main: 2
InPriority Thread:2(priority: 5)
In main: 3
InPriority Thread:3(priority: 5)
In main: 4
InPriority Thread:4(priority: 5)
main ending
Priority Threadterminating
setPriority() 메소들를 이용하여 우선순위를 바꾸어 보자
package thread.priority;
class Priority implements Runnable{
Thread thrd;
static String currentName;
int count;
static boolean stop = false;
Priority(String name) {
thrd = new Thread(this, name);
count = 0;
currentName = name;
}
public void run( ) {
System.out.println(thrd.getName( ) + " starting");
do {
count ++;
} while (stop == false && count < 10000000);
stop = true;
System.out.println(thrd.getName( ) + " terminating.");
}
}
public class Test2 {
public static void main(String[] args) {
Priority mt1 = new Priority("Max");
Priority mt2 = new Priority("Min");
Priority mt3 = new Priority("Norm");
mt1.thrd.setPriority(Thread.MAX_PRIORITY);//우선순위를 매긴다
mt2.thrd.setPriority(Thread.MIN_PRIORITY);
mt3.thrd.setPriority(Thread.NORM_PRIORITY);
mt1.thrd.start( );
mt2.thrd.start( );
mt3.thrd.start( );
try {
mt1.thrd.join( );
mt2.thrd.join( );
mt3.thrd.join( );
}
catch(InterruptedException e) {
System.out.println("Main interrupted!");
}
System.out.println("\nHigh priority count : " + mt1.count);
System.out.println("Min priority count : " + mt2.count);
System.out.println("Norm priority count : " + mt3.count);
}
}
스레드의 우선순위를 바꾸고 실행시켰는데 우선순위를 먼저 매긴것이 출력이 먼저 되었다. 하지만 계속 돌려본 결과 결과는 다양했고 컴퓨터 성능이 환경에따라 달라지는것일거 같다.
좀더 이부분은 공부를해서 활용을 해볼수 있도록 해야겠다.