36일차 전화번호부 8단계 참조 >
프로그램의 실행요청은 컴퓨터 사용자에 의해 이뤄지지만, 실질적인 프로그램의 실행은
( )에 의해서 이뤄진다.
메소드영역, 스택 영역, 힙 영역 으로 구성된다.
이렇듯 할당된 메모리 공간을 기반으로 실행 중에 있는 프로그램을 가리켜 ( )라 한다.
따라서 ( )를 간단히 '실행중인 프로그램'으로 설명하기도 한다.
package com.test.memo;
class showThread extends Thread {
String threadName;
showThread(String threadName) {
this.threadName = threadName;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(threadName);
try {
sleep(100); // Runnable을 구현했을때는 사용못하는 메서드
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Practice {
public static void main(String[] args) {
showThread s1 = new showThread("멋진 쓰레드");
showThread s2 = new showThread("이쁜 쓰레드");
s1.start();
s2.start();
}
}
Thread클래스를 상속받아 쓰레드를 생성하고, run()메서드를 재정의하여 각 쓰레드가 실행될 때 수행할 작업을 정의한다. > 각 쓰레드가 for루프를 돌며 0부터 99까지 숫자를 출력하고, sleep(100)메서드를 호출해 각 쓰레드가 출력하고 일정시간 멈추도록 한다.
start() 메서드로 각 쓰레드를 실행시킴으로써 이루어진다.쓰레드의 우선순위를 가져오는 메소드?
getPriority()쓰레드의 우선순위를 설정하는 메소드?
setPriority()쓰레드의 우선순위를 설정할 때 쓰는 상수는? > 최소1 ~ 최대 10
Thread.MAX_PRIORITY 10 > 최대 우선순위
Thread.NORM_PRIORITY 5 > 기본 우선순위
Thread.MIN_PRIORITY 1 > 최소 우선순위
4번 5번 메소드 괄호 안에 넣을 수 있다
myThread.setPriority(Thread.MAX_PRIORITY); // 쓰레드의 우선순위를 최대 우선순위로 설정
쓰레드의 라이프 사이클(Life Cycle)은?
New 상태
Runnable 상태
Blocked 상태
Dead 상태
package com.test.memo;
class Sum {
private int num;
Sum() {
num = 0; // 숫자를 받으면 AddThread에서 super로 값을 받아줘야함
}
void addNum(int n) {
num += n;
}
int getNum() {
return num;
}
}
class AddThread extends Sum implements Runnable {
int start;
int end;
AddThread(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public void run() {
for (int i = start; i <= end; i++) {
addNum(i);
}
}
}
class Practice {
public static void main(String[] args) {
AddThread a1 = new AddThread(1, 50);
AddThread a2 = new AddThread(51, 100);
Thread t1 = new Thread(a1);
Thread t2 = new Thread(a2);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1~100까지의 합 : " + (a1.getNum() + a2.getNum()));
}
}

package com.test.memo;
class AddThread extends Thread {
int start;
int end;
int num;
AddThread(int start, int end) {
this.start = start;
this.end = end;
num = 0;
}
void addNum(int n) {
num += n;
}
int getNum() {
return num;
}
@Override
public void run() {
for (int i = start; i <= end; i++) {
addNum(i);
}
}
}
class Practice {
public static void main(String[] args) {
AddThread a1 = new AddThread(1, 50);
AddThread a2 = new AddThread(51, 100);
Thread t1 = new Thread(a1);
Thread t2 = new Thread(a2);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1~100까지의 합 : " + (a1.getNum() + a2.getNum()));
}
}
package com.test.memo;
import javax.xml.catalog.Catalog;
class Increment {
private int num;
void Increment() {
num++;
}
int getNum() {
return num;
}
}
class IncThread extends Thread {
Increment inc;
IncThread(Increment inc) {
this.inc = inc;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
for (int j = 0; j < 10000; j++) { // 문제에서 중첩된 반복문이라 했으므로
inc.Increment();
}
}
}
}
class Practice {
public static void main(String[] args) {
Increment inc = new Increment();
IncThread i1 = new IncThread(inc);
IncThread i2 = new IncThread(inc);
IncThread i3 = new IncThread(inc);
i1.start();
i2.start();
i3.start();
try {
i1.join();
i2.join();
i3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(inc.getNum());
}
}//출력값이 다 다르지만 3억이 넘지 않는다.
6번 문제는 동기화가 제대로 되지 않아 잘못된 값을 출력할 확률이 높다. 이 것을 동기화 시키는 두 가지 방법으로 동기화가 제대로 이루어 지도록 하자.
동기화 메소드
public synchronized void increment()
{
num++;
}
동기화블럭
public void increment()
{
synchronized(this)//this객체 주소값을 열쇠로 사용
{
num++;
}
}
메소드보다 블럭으로 처리하는것이 더 좋다.
동기화 블럭만 다른 스레드들이 접근하지 못하는것이고, 다른 메서드에서 이 동기화블럭은 접근 가능하다.
풀이
package com.test.memo;
class Increment {
private int num;
Increment() {
num = 0;
}
synchronized void increment() { // 동기화 메소드
// synchronized (this) { > 동기화 블럭
num++;
// }
}
int getNum() {
return num;
}
}
class IncThread extends Thread {
private Increment inc;
IncThread(Increment inc) {
this.inc = inc;
}
@Override
public void run() {
for (int i = 0; i < 100000000; i++) {
// for (int j = 0; j < 10000; j++) { // 동기화에 중첩 반복문을 사용하면 데드락 발생 가능 ( 무한루프에 빠)
inc.increment();
// }
}
}
}
class Practice {
public static void main(String[] args) {
Increment inc = new Increment();
IncThread i1 = new IncThread(inc);
IncThread i2 = new IncThread(inc);
IncThread i3 = new IncThread(inc);
i1.start();
i2.start();
i3.start();
try {
i1.join();
i2.join();
i3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(inc.getNum());
}
}//300000000 출
문제 8 폴더에 있는 SyncObjectKey.java를 적절하게 Key를 사용하여 동기화해보자.(두 가지 방법)
키를 이용해 동기화를 수행하려면, 동기화에 사용할 객체가 필요하다.
두 개의 키를 사용해서
package com.test.memo;
class IHaveTwoNum {
int num1 = 0;
int num2 = 0;
// 동기화에 사용할 객체 생성
private final Object key1 = new Object();
private final Object key2 = new Object();
public void addOneNum1() {
synchronized (key1) {
num1 += 1;
}
}
public void addTwoNum1() {
synchronized (key1) {
num1 += 2;
}
}
public void addOneNum2() {
synchronized (key2) {
num2 += 1;
}
}
public void addTwoNum2() {
synchronized (key2) {
num2 += 2;
}
}
public void showAllNums() {
System.out.println("num1: " + num1);
System.out.println("num2: " + num2);
}
}
class AccessThread extends Thread {
IHaveTwoNum twoNumInst;
public AccessThread(IHaveTwoNum inst) {
twoNumInst = inst;
}
public void run() {
twoNumInst.addOneNum1();
twoNumInst.addTwoNum1();
twoNumInst.addOneNum2();
twoNumInst.addTwoNum2();
}
}
class Practice {
public static void main(String[] args) {
IHaveTwoNum numInst = new IHaveTwoNum();
AccessThread at1 = new AccessThread(numInst);
AccessThread at2 = new AccessThread(numInst);
at1.start();
at2.start();
try {
at1.join();
at2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
numInst.showAllNums();
}
}//num1 :6 / num2 : 6 출
this와 한개의 키만 사용해서
package com.test.memo;
class IHaveTwoNum {
int num1 = 0;
int num2 = 0;
// 동기화에 사용할 객체 생성
private final Object key1 = new Object();
public void addOneNum1() {
synchronized (this) {
num1 += 1;
}
}
public void addTwoNum1() {
synchronized (this) {
num1 += 2;
}
}
public void addOneNum2() {
synchronized (key1) {
num2 += 1;
}
}
public void addTwoNum2() {
synchronized (key1) {
num2 += 2;
}
}
public void showAllNums() {
System.out.println("num1: " + num1);
System.out.println("num2: " + num2);
}
}
class AccessThread extends Thread {
IHaveTwoNum twoNumInst;
public AccessThread(IHaveTwoNum inst) {
twoNumInst = inst;
}
public void run() {
twoNumInst.addOneNum1();
twoNumInst.addTwoNum1();
twoNumInst.addOneNum2();
twoNumInst.addTwoNum2();
}
}
class Practice {
public static void main(String[] args) {
IHaveTwoNum numInst = new IHaveTwoNum();
AccessThread at1 = new AccessThread(numInst);
AccessThread at2 = new AccessThread(numInst);
at1.start();
at2.start();
try {
at1.join();
at2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
numInst.showAllNums();
}
}
쓰레드의 실행순서는 소스코드가 나열된 순서와 다를 수 있다.
독자 실행.
작가 실행.
쓰레드의 동시 접근을 막는 동기화
package com.test.memo;
class NewsPaper {
String todayNews;
void setTodayNews(String news) {
todayNews = news;
}
String getTodayNews() {
return todayNews;
}
}
class NewsWriter extends Thread {
NewsPaper paper;
public NewsWriter(NewsPaper paper) {
this.paper = paper;
}
@Override
public void run() {
paper.setTodayNews("자바의 열기가 뜨겁습니다.");
}
}
class NewsReader extends Thread {
NewsPaper paper;
public NewsReader(NewsPaper paper) {
this.paper = paper;
}
@Override
public void run() {
System.out.println("오늘의 뉴스: " + paper.getTodayNews());
}
}
class Practice {
public static void main(String[] args) {//쓰레드는 총 3개 > 메인, NewsReader, NewsWriter
NewsPaper paper = new NewsPaper();
NewsReader reader = new NewsReader(paper); //힙 메모리를 공유해서 쓸 수 있음
NewsWriter writer = new NewsWriter(paper);
reader.start(); //쓰레드는 실행흐름을 알 수 없기 때문에, reader가 먼저 실행될지, writer가 먼저 실행될지 알 수없다.
writer.start();
try {
reader.join(); //reader가 종료될 때 까지 다른 쓰레드가 기다린다.
writer.join(); //writer가 종료될 때 까지 다른 쓰레드가 기다린다.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}//오늘의 뉴스: null > reader가 먼저 실행된것
public final void **wait()** throws InterruptedException > 동기화처리를 반드시 해줘야 한다.
final이므로, 메소드의 오버라이딩을 허용하지 않는다 > InterruptedException을 예외처리 해야한다.
public final void notify()public final void notifyAll() > 동기화처리를 반드시 해줘야한다.package com.test.memo;
class NewsPaper {
String todayNews;
boolean isTodayNews;
void setTodayNews(String news) {
todayNews = news;
isTodayNews = true;
synchronized (this) {
notifyAll(); // 기다리고 있는 쓰레드를 모두 깨우는 메소드
//notify(); 로 사용한다면, 하나만 깨우기 때문에, 출력이 한번만 나온다.
}
}
String getTodayNews() {
if (isTodayNews == false) { //작성된 뉴스가 없으면
try {
synchronized (this) { //동기화 블럭 필수
wait(); // 쓰레드를 기다리게 하는 메소드
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return todayNews; //작성된 뉴스가 존재하면 바로 반환
}
}
class NewsWriter extends Thread {
NewsPaper paper;
public NewsWriter(NewsPaper paper) {
this.paper = paper;
}
public void run() {
paper.setTodayNews("자바의 열기가 뜨겁습니다.");
}
}
class NewsReader extends Thread {
NewsPaper paper;
public NewsReader(NewsPaper paper) {
this.paper = paper;
}
public void run() {
System.out.println("오늘의 뉴스: " + paper.getTodayNews());
}
}
class Practice {
public static void main(String[] args) {
NewsPaper paper = new NewsPaper();
NewsReader reader1 = new NewsReader(paper);// 독자 1
NewsReader reader2 = new NewsReader(paper);// 독자 2
NewsWriter writer = new NewsWriter(paper);
try {
reader1.start();
reader2.start(); >> 작성된 뉴스가 없기때문에 위 코드에서 wait()상태에 있을 것
Thread.sleep(1000); // 위 reader를 먼저 실행시키기 위해 강제로 1초란 시간을 쓰레드에 준것이다. > 1초동안 잠에듬
writer.start(); //1초 후에 writer실행된다. > run()메소드가 자동 호출되서, NewsPaper에 todayNews에 문자열이 들어갈 것
// > notifyAll()메소드로 멈췄던 reader1, reader2의 start() 가 마저 실행되면서, 출력
reader1.join();
reader2.join();
writer.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//오늘의 뉴스: 자바의 열기가 뜨겁습니다.
//오늘의 뉴스: 자바의 열기가 뜨겁습니다.
두 메소드 wait와 notifyAll(notify)이 서로 다른 두 쓰레드에 의해서 동시에 각각 호출되는 것 조차 허용되지 않아야 함을 뜻하는 것
package com.test.memo;
import java.util.Scanner;
class IntegerCom { //정수를 저장하고 반환하는 기능
int num = 0;
boolean isNewNum = false;
void setNum(int n) { //새로운 정수를 받아서 저장
synchronized (this) {
if (isNewNum == true) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num = n;
isNewNum = true;
notify();
}
}
int getNum() { //저장된 정수 반환
int retNum;
synchronized (this) {
if (isNewNum == false) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
retNum = num;
isNewNum = false;
notify();
}
return retNum;
}
}
class IntegerSum extends Thread {
IntegerCom com;
int sum;
IntegerSum(IntegerCom com) {
this.com = com;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
sum += com.getNum();
}
System.out.println("입력된 정수의 합 : " + sum);
}
}
public class Practice {
public static void main(String[] args) {
IntegerCom com = new IntegerCom();
IntegerSum sum = new IntegerSum(com);
sum.start();
Scanner sc = new Scanner(System.in);
System.out.println("총 5개의 정수 입력 ...");
for (int i = 0; i < 5; i++) {
com.setNum(sc.nextInt());
}
try {
sum.join(); //스레드의 작업이 완료될때 까지 대리
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
사용자 입력을 받는 동안 스레드가 대기하고, 입력이 완료되면 스레드가 그 값을 읽어 합계를 계산하는 기능 제공
동기화와 같은 역할을 하지만 잠금 영역과 잠금을 푸는 영역을 수동적으로 설정할 수 있다.
synchronized 보다 유연하고 강력한 기능을 가지고 있다.
데드락을 방지하고, 스레드 간의 공정한 동시성을 제어할 수 있다.
RenntrantLock객체가 다른 스레드에서 변경되지 않도록 하려면 final로 선언하는게 좋다
synchronized 키워드의 대체
ReentrantLock()이 있다.ReentrantLock()의 메소드
lock() : 다른 쓰레드가 진입하지 못하게 잠근다.
unlock() : 다른 쓰레드의 진입이 가능하게 문을 연다.
lock()메소드는 한번 호출되면, unlock()이 호출되기 전까지 lock메소드의 호출이 불가능하다.
lock()을 걸었으면 반드시 unlock()되어야 하기때문에 filnally구문에서 unlock()을 호출할 수 있다.
tryLock(): 잠금을 시도합니다. 잠금을 즉시 획득할 수 없으면 즉시 실패합니다.
tryLock(long timeout, TimeUnit unit): 일정 시간 동안 잠금을 시도합니다. 시간이 초과되면 실패합니다.
newCondition(): Condition 객체를 생성합니다. 조건 변수를 사용하여 특정 조건에 대해 스레드를 대기시키고 깨울 수 있습니다.
예제
package com.test.memo;
import java.util.concurrent.locks.ReentrantLock;
class Num {
private int num;
private final ReentrantLock key = new ReentrantLock();
void addNum() {
key.lock();
try {
num++; // 잠금 시키고, 값을 증가시키고 잠금을 해제
} finally {
key.unlock();
}
}
int getNum() { // 동기화되어 있지 않아 여러 스레드가 호출한다면 올바르지 않은 값 반환할 수 있음
return num;
}
// int getNum() {
// key.lock();
// try {
// return num;
// } finally {
// key.unlock();
// }
// }
}
class Apple extends Thread {
Num num;
public Apple(Num num) {
this.num = num;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
num.addNum();
}
}
}
public class Organize {
public static void main(String[] args) {
Num num = new Num();
Apple a1 = new Apple(num);
Apple a2 = new Apple(num);
a1.start();
a2.start();
try {
a1.join();
a2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("사과 총 개수 : " + num.getNum());
}
}//사과 총 개수 : 2000
Synchronized와 ReentrantLock
둘의 기능은 비슷하지만, 암묵적으로 동기화 구문을 지정하는 synchronized와 달리 ReentrantLock의 동기화 구문은 lock, unlock메소드를 통해 명시적으로 표현된다는 차이가 있다.
스레드 간의 통신과 스레드 실행을 조정하는 데 사용되는 Java의 인터페이스
Lock 객체와 함께 사용되며, Lock 객체를 통해 스레드의 잠금을 관리하고 Condition을 사용하여 스레드 간의 신호를 주고받아, 스레드의 실행을 조절하고 동기화된 상태에서 특정 조건이 충족될 때 까지 스레드를 대기시킬 수 있다.
synchronized에서 상태를 제어하는 메소드 > wait, notify등의 메소드들이 synchronized가 아닌 상태에서 사용하면 IlleglaMonitorStateException이 발생한다.
Condition 메소드
아래 메소드들 역시 한번에 하나의 메소드만 호출될 수 있도록 동기화 처리를 해야한다.
단, 반드시 앞서보인 ReentrantLock 인스턴스 기반으로 동기화 처리가 되어야 한다(synchronized 기반이 아닌)
await(): 현재 스레드를 대기 상태로 전환하고, 다른 스레드가 signal() 또는 signalAll() 메서드를 호출할 때까지 기다립니다. 스레드는 Lock 객체를 소유하고 있는 상태에서 이 메서드를 호출해야 합니다.
synchronized에서의 wait()메서드와 같다.
awaitUninterruptibly(): await()와 동일하지만 인터럽트를 무시하고 대기합니다.
await(long time, TimeUnit unit): 일정 시간 동안 대기하고, 시간이 경과하거나 다른 스레드가 signal() 또는 signalAll() 메서드를 호출할 때까지 기다립니다.
awaitUntil(Date deadline): 지정된 시간까지 대기하고, 시간이 경과하거나 다른 스레드가 signal() 또는 signalAll() 메서드를 호출할 때까지 기다립니다.
signal(): 대기 중인 하나의 스레드를 깨웁니다. 선택된 스레드는 대기 상태에서 벗어나서 실행 대기 상태로 전환됩니다.
synchronized에서의 norify()와 같다.
signalAll(): 대기 중인 모든 스레드를 깨웁니다. 모든 스레드는 대기 상태에서 벗어나서 실행 대기 상태로 전환됩니다.
synchronized에서의 notifyAll()과 같다.
예제 > Condition인스턴스 기반의 실행순서 동기화 사례
두 개의 쓰레드를 생성해, 하나는 프로그램 사용자로부터 문자열을 입력받고, 다른 하나는 입력받은 문자열을 출력하는 쓰레드다.
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
import java.util.Scanner;
class StringComm
{
String newString;
boolean isNewString=false;
private final ReentrantLock entLock=new ReentrantLock();//스레드의 동기화를 제어하기 위해
private final Condition readCond=entLock.newCondition();
private final Condition writeCond=entLock.newCondition();
public void setNewString(String news)
{
entLock.lock();
try
{
if(isNewString==true)
writeCond.await();
newString=news;
isNewString=true;
readCond.signal();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
finally
{
entLock.unlock();
}
}
public String getNewString()
{
String retStr=null;
entLock.lock();
try
{
if(isNewString==false)
readCond.await();
retStr=newString;
isNewString=false;
writeCond.signal();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
finally
{
entLock.unlock();
}
return retStr;
}
}
class StringReader extends Thread
{
StringComm comm;
public StringReader(StringComm comm)
{
this.comm=comm;
}
public void run()
{
Scanner keyboard=new Scanner(System.in);
String readStr;
for(int i=0; i<5; i++)
{
readStr=keyboard.nextLine();
comm.setNewString(readStr);
}
}
}
class StringWriter extends Thread
{
StringComm comm;
public StringWriter(StringComm comm)
{
this.comm=comm;
}
public void run()
{
for(int i=0; i<5; i++)
System.out.println("read string: "+comm.getNewString());
}
}
class ConditionSyncStringReadWrite
{
public static void main(String[] args)//쓰레드 1
{
StringComm strComm=new StringComm();
StringReader sr=new StringReader(strComm); //쓰레드 2
StringWriter sw=new StringWriter(strComm); //쓰레드 3
System.out.println("입출력 쓰레드의 실행...");
sr.start();
sw.start();
}
}
예제 1 - 쓰레드의 스케줄링과 우선순위 컨트롤
class MessageSendingThread extends Thread
{
String message;
int priority;
public MessageSendingThread(String str)
{
message=str;
}
public void run()
{
for(int i=0; i<1000000; i++)
System.out.println(message+"("+getPriority()+")");//우선순위 가져오기
}
}
class PriorityTestOne
{
public static void main(String[] args)
{
MessageSendingThread tr1=new MessageSendingThread("First");
MessageSendingThread tr2=new MessageSendingThread("Second");
MessageSendingThread tr3=new MessageSendingThread("Third");
tr1.start();
tr2.start();
tr3.start();
}
}
스케줄링에 사용되는 알고리즘의 기본원칙
예제 2 - 우선순위
class MessageSendingThread extends Thread
{
String message;
public MessageSendingThread(String str, int prio)
{
message=str;
setPriority(prio);
}
public void run()
{
for(int i=0; i<1000000; i++)
{
System.out.println(message+"("+getPriority()+")");
try
{
sleep(1); //0.001초동안 스레드 정지
// sleep은 Thread 클래스의 static 메소드로서, 실행흐름을 일시적으로 멈추는 역할을 한다.
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
class PriorityTestThree
{
public static void main(String[] args)
{
MessageSendingThread tr1
=new MessageSendingThread("First", Thread.MAX_PRIORITY); //최대 우선순위 10
MessageSendingThread tr2
=new MessageSendingThread("Second", Thread.NORM_PRIORITY); //중간 우선순위 5
MessageSendingThread tr3
=new MessageSendingThread("Third", Thread.MIN_PRIORITY); //최소 우선순위 1
//static상수로 가져다 쓰는게 좋다.(운영체제에 따른 차이를 최소하기 위해서)
tr1.start();
tr2.start();
tr3.start();
}
}
예제3 - 동기화처리
동기화처리를 하면 성능이 떨어진 > 느려짐 (Vector)
오전문제
class Increment
{
int num=0;
public void increment(){ num++; }
// public synchronized void increment(){ num++; }
public int getNum() { return num; }
}
class IncThread extends Thread
{
Increment inc;
public IncThread(Increment inc)
{
this.inc=inc;
}
public void run()
{
for(int i=0; i<10000; i++)
for(int j=0; j<10000; j++)
inc.increment();
}
}
class ThreadSyncError
{
public static void main(String[] args)
{
Increment inc=new Increment();
IncThread it1=new IncThread(inc);
IncThread it2=new IncThread(inc);
IncThread it3=new IncThread(inc);
it1.start();
it2.start();
it3.start();
try
{
it1.join();
it2.join();
it3.join();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
System.out.println(inc.getNum());
}
}
예제 4 - 수학연산
class Calculator
{
int opCnt=0;
public int add(int n1, int n2)
{
opCnt++;
return n1+n2;
}
public int min(int n1, int n2)
{
opCnt++;
return n1-n2;
}
public void showOpCnt()
{
System.out.println("총 연산 횟수: "+opCnt);
}
}
class AddThread extends Thread
{
Calculator cal;
public AddThread(Calculator cal) { this.cal=cal; }
public void run()
{
System.out.println("1+2="+cal.add(1, 2));
System.out.println("2+4="+cal.add(2, 4));
}
}
class MinThread extends Thread
{
Calculator cal;
public MinThread(Calculator cal) { this.cal=cal; }
public void run()
{
System.out.println("2-1="+cal.min(2, 1));
System.out.println("4-2="+cal.min(4, 2));
}
}
class ThreadSyncMethod
{
public static void main(String[] args)
{
Calculator cal=new Calculator();
AddThread at=new AddThread(cal);
MinThread mt=new MinThread(cal);
at.start(); //자동 run()호출
mt.start();
try
{
at.join();
mt.join();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
cal.showOpCnt();
}
}
문제 1
package com.test.memo;
import java.util.concurrent.locks.ReentrantLock;
class IHaveTwoNum {
int num1 = 0;
int num2 = 0;
// Object key1 = new Object(); 이렇게 할 필요없이 아래처럼 객체 생성해서 lock
private final ReentrantLock key1 = new ReentrantLock();
private final ReentrantLock key2 = new ReentrantLock();
public void addOneNum1() {
key1.lock();
try {
num1 += 1;
} finally {
key1.unlock();
}
}
public void addTwoNum1() {
key1.lock();
try {
num1 += 2;
} finally {
key1.unlock();
}
}
public void addOneNum2() {
key2.lock();
try {
num2 += 1;
} finally {
key2.unlock();
}
}
public void addTwoNum2() {
key2.lock();
try {
num2 += 2;
} finally {
key2.unlock();
}
}
public void showAllNums() {
System.out.println("num1: " + num1);
System.out.println("num2: " + num2);
}
}
class AccessThread extends Thread {
IHaveTwoNum twoNumInst;
public AccessThread(IHaveTwoNum inst) {
twoNumInst = inst;
}
public void run() {
twoNumInst.addOneNum1();
twoNumInst.addTwoNum1();
twoNumInst.addOneNum2();
twoNumInst.addTwoNum2();
}
}
public class Organize {
public static void main(String[] args) {
IHaveTwoNum numInst = new IHaveTwoNum();
AccessThread at1 = new AccessThread(numInst);
AccessThread at2 = new AccessThread(numInst);
at1.start();
at2.start();
try {
at1.join();
at2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
numInst.showAllNums();
}
}
ReentrantLock클래스는 명시적으로 lock() 및 unlock() 메서드를 호추랗여 잠금을 획득 및 해제할 수 있다. > Collection객체를 사용하지 않아도 스레드의 동기화를 제어할 수 있다.
Condition은 주로 ReentrantLock의 상태를 기다리거나 조정하기 위해 사용된다.문제 2
ReentrantLock 인스턴스를 대상으로 newCondition이라는 이름의 메소드를 호출하면, Condition형 인스턴스가 반환된다(정확히는 Condition 인터페이스를 구현하는 인스턴스의 참조 값이 반환된다). 그리고 반환된 인스턴스를 대상으로 다음의 메소드를 호출할 수 있다.

이런 형태로 생성해 메서드 호출 가능
package com.test.memo;
import java.util.Scanner;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class Comm {
String newString;
boolean isNewString = false; // 새로운 문자열이 설정 되었는지
private final ReentrantLock ent = new ReentrantLock();
private final Condition read = ent.newCondition();
private final Condition write = ent.newCondition();
void setNewString(String input) {
ent.lock();
try {// if가 아닌 while로도 가능
if (isNewString == true) {// 새로운 문자열이 있으면 write대기
write.await();
}
newString = input;
isNewString = true;
read.signal(); // 대기중인 스레드에게 신호
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
ent.unlock();
}
}
String getNewString() {
String str = null;
ent.lock();
try {
if (!isNewString) { // 새로운 문자열이 없으면
read.await(); // 새로운 문자열이 설정될때 까지 대기
}
str = newString;
isNewString = false; // write에 보내줄거니까 flase로 변환
write.signal(); // 대기중인 스레드에게 신호
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
ent.unlock();
}
return str;
}
}
class StringReader extends Thread {
Comm com;
StringReader(Comm com) {
this.com = com;
}
@Override
public void run() {
Scanner sc = new Scanner(System.in);
String read;
for (int i = 0; i < 5; i++) {
read = sc.nextLine();
com.setNewString(read);
}
}
}
class StringWriter extends Thread {
Comm com;
StringWriter(Comm com) {
this.com = com;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("read :" + com.getNewString());
}
}
}
public class Organize {
public static void main(String[] args) {
Comm strCom = new Comm();
StringReader sr = new StringReader(strCom);
StringWriter sw = new StringWriter(strCom);
System.out.println("입출력 쓰레드 실행...");
sr.start();
sw.start();
}
}

Callable
Callable은 Runnable과 비슷한 인터페이스지만, 작업의 결과를 반환하고 예외를 던질 수 있다.
Callable은 java.util.concurrent 패키지에 속해있다.
call() 메서드를 구현해야한다. > Functional Interface
일반적으로 ExecutorService의 submit 메서드를 사용하여 Callable을 실행한다.
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 비동기 작업을 수행하고 결과를 반환
return 42;
}
}
Future
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Integer> callable = new MyCallable();
Future<Integer> future = executor.submit(callable);
// 작업이 완료될 때까지 기다리고 결과를 가져옴
Integer result = future.get();
System.out.println("Result: " + result);
executor.shutdown();
}
}
Future는 비동기 작업의 상태를 추적하고 결과를 가져오는 데 사용된다.
예를 들어, get() 메서드를 호출하면 작업이 완료될 때까지 현재 스레드를 블록하고, 작업이 완료되면 결과를 반환한다.
쓰레드 실행 흐름(속도) 차이
class MakeThreadMultiDemo {
public static void main(String[] args) {
Runnable task1 = () -> { // 20 미만 짝수 출력
try {
for(int i = 0; i < 20; i++) {
if(i % 2 == 0)
System.out.print(i + " ");
Thread.sleep(100); // 0.1초간 잠을 잔다.
}
} catch(InterruptedException e) {
e.printStackTrace();
}
};
Runnable task2 = () -> { // 20 미만 홀수 출력
try {
for(int i = 0; i < 20; i++) {
if(i % 2 == 1)
System.out.print(i + " ");
Thread.sleep(100);
}
} catch(InterruptedException e) {
e.printStackTrace();
}
};
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
t1.start();
t2.start();
}
}
/*
//실행 결과
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
*/
synchronized
class Counter {
int count = 0;
public void increment() {
synchronized(this) {
count++;
}
}
public void decrement() {
synchronized(this) {
count--;
}
}
public int getCount() { return count; }
}
class MutualAccessSyncBlock {
public static Counter cnt = new Counter();
public static void main(String[] args) throws InterruptedException {
Runnable task1 = () -> {
for(int i = 0; i<1000; i++)
cnt.increment();
};
Runnable task2 = () -> {
for(int i = 0; i<1000; i++)
cnt.decrement();
};
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(cnt.getCount());
}
}
/*
실행결과
0
*/
동기화 필요성
class Counter {
int count = 0; // 두 쓰레드에 의해 공유되는 변수
public void increment() {
count++; // 첫 번째 쓰레드에 의해 실행되는 문장
}
public void decrement() {
count--; // 또 다른 쓰레드에 의해 실행되는 문장
}
public int getCount() { return count; }
}
class MutualAccess {
public static Counter cnt = new Counter();
public static void main(String[] args) throws InterruptedException {
Runnable task1 = () -> {
for(int i = 0; i<1000; i++)
cnt.increment(); // 값을 1 증가
};
Runnable task2 = () -> {
for(int i = 0; i<1000; i++)
cnt.decrement(); // 값을 1 감소
};
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
t1.start();
t2.start();
t1.join(); // t1이 참조하는 쓰레드의 종료를 기다림
t2.join(); // t2가 참조하는 쓰레드의 종료를 기다림
System.out.println(cnt.getCount());
}
}
/*
실행결과
39
*/
"둘 이상의 쓰레드가 동일한 변수에 접근하는 것은 문제를 일으킬 수 있다."
둘 이상의 쓰레드가 동일한 메모리 공간에 접근해도 문제가 발생하지 않도록 '동기화(synchoronization)'을 해야한다.
쓰레드 풀
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
class ExecutorsDemo {
public static void main(String[] args) {
Runnable task = () -> { // 쓰레드에게 시킬 작업 > run()인것이 된다.
int n1 = 10;
int n2 = 20;
String name = Thread.currentThread().getName();
System.out.println(name + ": " + (n1 + n2));
};
ExecutorService exr = Executors.newSingleThreadExecutor();
exr.submit(task); // 쓰레드 풀에 작업을 전달하면서 run()실행
System.out.println("End " + Thread.currentThread().getName());
exr.shutdown(); // 쓰레드 풀과 그 안에 있는 쓰레드의 소멸
}
}
newSingleThreadExecutor() 풀 안에 하나의 쓰레드만 생성하고 유지한다.newFixedThreadPool() 풀 안에 인자로 전달된 수의 쓰레드를 생성하고 유지한다.newCachedThreadPool() 풀 안의 쓰레드의 수를 작업의 수에 맞게 유동적으로 관리한다.쓰레드 풀 여러개
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
class ExecutorsDemo2 {
public static void main(String[] args) {
Runnable task1 = () -> {
String name = Thread.currentThread().getName();
System.out.println(name + ": " + (5 + 7));
};
Runnable task2 = () -> {
String name = Thread.currentThread().getName();
System.out.println(name + ": " + (7 - 5));
};
ExecutorService exr = Executors.newFixedThreadPool(2);
exr.submit(task1);
exr.submit(task2);
exr.submit(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + ": " + (5 * 7));
});
exr.shutdown();
}
}
/*
실행결과
pool-1-thread-2: 2
pool-1-thread-1: 12
pool-1-thread-2: 35
*/
Collable & Future
Runnable 인터페이스의 run()메소드는 반환형이 void이므로 작업의 결과를 return할 수 없다.
Runnable과 비슷한 인터페이스지만, 작업의 결과를 반환하고 예외를 던질 수 있다.
Callable은 java.util.concurrent 패키지에 속해 있고, call()메서드를 구현해야한다 > 추상메서드가 한개 이므로 Functional Interface로 람다가 가능하다.
import java.util.concurrent.Future;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ExecutionException;
class CallableDemo {
public static void main(String[] args)
throws InterruptedException, ExecutionException {
Callable<Integer> task = () -> { //call()메소드 호출
int sum = 0;
for(int i = 0; i < 10; i++)
sum += i;
return sum;
};
ExecutorService exr = Executors.newSingleThreadExecutor();
Future<Integer> fur = exr.submit(task);
Integer r = fur.get();
System.out.println("result: " + r);
exr.shutdown();
}
}
/*
실행결과
result: 45
*/
ReentrantLock()
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
class Counter {
int count = 0;
ReentrantLock criticObj = new ReentrantLock();
public void increment() {
criticObj.lock(); //동기화 구조
try {
count++;
} finally {
criticObj.unlock();
}
}
public void decrement() {
criticObj.lock();
try {
count--;
} finally {
criticObj.unlock();
}
}
public int getCount() { return count; }
}
class MutualAccessReentrantLock {
public static Counter cnt = new Counter();
public static void main(String[] args) throws InterruptedException {
Runnable task1 = () -> {
for(int i = 0; i < 1000; i++)
cnt.increment();
};
Runnable task2 = () -> {
for(int i = 0; i < 1000; i++)
cnt.decrement();
};
ExecutorService exr = Executors.newFixedThreadPool(2);
exr.submit(task1);
exr.submit(task2);
exr.shutdown(); //쓰레드 풀에 전달된 작업이 마무리되면 폐쇄하라고 명령을 하는데 바로 반환되기 때문에 아래 문장이 꼭 필요한 것이다.
exr.awaitTermination(100, TimeUnit.SECONDS);//쓰레드 풀에 전달된 모든 작업이 100이내에 작업이 완료되면 true, 아니면 false반환
System.out.println(cnt.getCount());
}
}
/*
실행 결과
0
*/
일반적으로 ExecutorService의 작업을 실행한 후에 shutdown() 메서드를 호출하고 나서 awaitTermination() 메서드를 호출하여 모든 작업이 완료될 때까지 기다린다.
동기화된 ArrayList<>
ArraList<>인스턴스 대상으로는 둘 이상의 쓰레드가 동시에 접근하는 코드를 ㅁ나들면 안된다.
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ListIterator;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
class SyncArrayList {
public static List<Integer> lst =
Collections.synchronizedList(new ArrayList<Integer>());
public static void main(String[] args) throws InterruptedException {
for(int i = 0; i < 16; i++)
lst.add(i);
System.out.println(lst);
Runnable task = () -> { //람다로 구현 Iterator()는 동기화처리되지 않는다.
ListIterator<Integer> itr = lst.listIterator();
//아래 주석처리 된 부분처럼 synchronized를 통해 따로 반복자를 동기화 해줘야한다.
while(itr.hasNext())
itr.set(itr.next() + 1); //itr가 가져온 값에 +1
};
/*
Runnable task = () -> { //이 코드로 해야 Iterator()까지 동기화 처리된것
synchronized(lst) { //동기화 블록의 내부를 실행할 때 lst에 다른 쓰레드의 접근을 허용하지 않는다.
ListIterator<Integer> itr = lst.listIterator();
while(itr.hasNext())
itr.set(itr.next() + 1);
}
};
*/
ExecutorService exr = Executors.newFixedThreadPool(3); //3개짜리 풀 생성
exr.submit(task);
exr.submit(task);
exr.submit(task); //쓰레드 3개 실행
exr.shutdown();
exr.awaitTermination(100, TimeUnit.SECONDS);
System.out.println(lst);
}
}
/*
실행결과
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 13, 14, 16, 17, 18]
*/
package com.test.memo;
public class Practice {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
}
}//main 출력
package com.test.memo;
public class Practice {
public static void main(String[] args) {
Runnable task1 = () -> { // 쓰레드에게 시킬 작업
for (int i = 0; i < 20; i += 2) {
System.out.print(i + " ");
}
};
Thread t1 = new Thread(task1);
t1.start();
}
}//0 2 4 6 8 10 12 14 16 18
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
class ExecutorsDemo {
public static void main(String[] args) {
Runnable task = () -> { // 쓰레드에게 시킬 작업
int n1 = 10;
int n2 = 20;
String name = Thread.currentThread().getName();
System.out.println(name + ": " + (n1 + n2));
};
ExecutorService exr = Executors.newSingleThreadExecutor(); //단일 쓰레드 생성 (쓰레드 풀로 사용할 쓰레드 생성)
exr.submit(task); // 쓰레드 풀에 작업을 전달하면서 run()이 실행 > 위에서는 람다로 표현
System.out.println("End " + Thread.currentThread().getName());
exr.shutdown(); // 쓰레드 풀과 그 안에 있는 쓰레드의 소멸
}
}
submit() : 쓰레드 풀에 작업 전달
shutdown() : 생성된 스레드 풀과 그 안에 존재하는 스레드 소멸
바로 종료되지 않고, 스레드가 실행 중이라면 해당 실행을 마치고 종료된다.(해당 실행이 끝나면 추가적으로 실행되지않음)
함수형 인터페이스는 람다로 구현 가능하다. > F3누르면 해당 코드 속성 볼 수 있다.
Runnable은 Functional Interface로 하나의 추상 메서드를 가지고 있는 인터페이스다.
Runnable은 run()메서드 하나만을 가지고 있기 때문에 Functional Interface다.
import java.util.concurrent.Future;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ExecutionException;
class CallableDemo {
public static void main(String[] args)
throws InterruptedException, ExecutionException {
Callable<Integer> task = () -> {
int sum = 0;
for(int i = 0; i < 10; i++)
sum += i;
return sum;
};
ExecutorService exr = Executors.newSingleThreadExecutor(); //스레드 한 개 생성
Future<Integer> fur = exr.submit(task); //task를 실행하고, 작어브이 결과를 나타내는 Future객체를 반환한다. > 작업의 상태 추척 및 완료되면 결과 얻을 수 있다.
Integer r = fur.get();
System.out.println("result: " + r);
exr.shutdown();
}
}
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ListIterator;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
class SyncArrayList {
public static List<Integer> lst =
Collections.synchronizedList(new ArrayList<Integer>());
public static void main(String[] args) throws InterruptedException {
for(int i = 0; i < 16; i++)
lst.add(i);
System.out.println(lst);
Runnable task = () -> {
synchronized(lst) {
ListIterator<Integer> itr = lst.listIterator();
while(itr.hasNext())
itr.set(itr.next() + 1);
}
};
ExecutorService exr = Executors.newFixedThreadPool(3);
exr.submit(task);
exr.submit(task);
exr.submit(task);
exr.shutdown();
exr.awaitTermination(100, TimeUnit.SECONDS);
System.out.println(lst);
}
}
1번 - 싱글 스레드
package com.test.memo;
import javax.swing.JOptionPane;
public class Practice {
public static void main(String[] args) {
String input = JOptionPane.showInputDialog("아무값이나 입력하세요");
System.out.println("입력한 값은 " + input + " 입니다.");
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
try {
Thread.sleep(1000);//사용자가 입력 후 숫자를 출력하고 1초뒤 종료
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

main쓰레드로 사용자의 입력을 받고, 화면에 출력하는 작업을 수행한다.
2번 - 멀티 스레드
package com.test.memo;
import javax.swing.JOptionPane;
public class Practice {
public static void main(String[] args) {
ExThread t1 = new ExThread();
t1.start();
String input = JOptionPane.showInputDialog("아무값이나 입력하세요");
System.out.println("입력한 값은 " + input + " 입니다.");
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ExThread extends Thread{
@Override
public void run() {
for(int i = 10; i > 0 ; i--) {
System.out.println(i);
}
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

ExThread가 시작되면 main 스레드는 사용자 입력을 기다리는JOptionPane.showInputDialog() 메서드에서 블록되어 멈추게 된다.
이렇게 되면 ExThread는 계속해서 실행되며 출력한다. 사용자가 입력을 완료하고 main 스레드가 다시 실행될 때, ExThread가 이미 시작되어 출력을 생성하고 있기 때문에 ExThread가 먼저 출력될 수 있다.
그래서 main 스레드가 JOptionPane.showInputDialog를 실행하는 동안 ExThread가 먼저 실행되고 출력되는 것이다.
