18일차 2023-03-31
1. 스레드
1-1) 프로그램과 프로세스의 차이
ex) 윈도우 키 눌러서 보이는 계산기는 프로그램, 하지만 이걸 실행시키면 메모리 올라감 그럼 이걸 프로세스라 부름.
1-2) 스레드란?
정의: 하나의 프로세스 안에서 실행되는 명령 흐름.
즉, 프로세스 안의 작은 프로그램이다.
자바는 멀티 스레딩을 지원, 이 지원을 위해 Thread클래스와 Runnable인터페이스 제공함.
1-3) 멀티 스레딩이란?
정의: 하나의 프로세스 안에서 여러 개의 스레드가 동시 작업하는 것.
자바에서 main메소드 이것도 하나의 main스레드라고 한다.
근데 단일 스레드임.
Thread클래스를 상속, runnable인터페이스를 상속=> 그래서 다른 스레드를 동작시킬 수 있음.
1-4) 스레드의 상태전이
start() 호출하면 Runnable => Running => Dead
Running에서 wait()나 Sleep() 호출되면 wait상태 3000(3초) 있다가=>다시 Runnable상태로 감 근데 notify()에서 어떤 걸 호출할지 모름 (pc os마음) (이건 wait()) 거의 안 씀
그래서 sleep() 씀 이것은 그런 거 없이 바로 Runable상태로 바로 감.
1-5) 만드는 방법:
1. java.lang.Thread 클래스를 상속받아 구현
2. java.lang.Runnable 인터페이스를 상속받아 구현
1-6) Thread의 주요 메소드
-start(): 스레드 시작, 스레드를 Runnable상태로 전이시킴, 스레드의 run()메소드 호출
.run(): 스케쥴러에 의해 Running상태가 되면 안의 코드 실행. (대기상태는 Runnable)에 위치)
run()메소드 오버라이딩: 개발자가 호출하는 것 아님. Running상태에 들어가면 자동으로 호출되는 메소드이다(이걸 콜백 메소드라 한다)
static int activeCount(): 현재 활성화된 스레드의 수를 리턴 (int)형
static Thread currentThread(): 현재 실행중인 스레드를 리턴()
setName(String name): 스레드의 이름을 주어진 이름으로 변경
String getName(): 스레드의 이름을 리턴
sleep(long milis): 1000분의 1초동안 늦게 실행 ex) sleep(3000) 하면 3초, sleep(7000) 하면 7초대기
static yield(): 현재 실행중인 스레드가 점유한 cpu를 내좋도록 한다.=> 자기와 우선순위가 같거나 높은 스레드에게 실행할 기회를 주는 것을 의미
setPriority(int priorty): 스레드의 우선순위 설정: MAX_PRIORITY, MIN_PRIORIT, NORM_PRIORIT
MAX_PRIORITY: 스레드가 가지는 최대 우선권 value: 10
MIN_PRIORITY: 스레드가 가지는 최소 우선권 value: 1
NORM_PRIORIT: 스레드가 가지는 디폴트 우선권 value: 5
join(): start() 호출뒤에 join메소드를 호출해야 되며, join()를 호출한 스레드가 다 실행된 상태(Dead상태)가 돼야 다른 스레드가 동작할 수 있다.
하지만, 콘솔창을 보면 맥스가 먼저 출력되지 않기도 한다.
이 의미는 우선권이지만 먼저 실행된다는 것은 아니라는 것을 의미
다른 스레드도 value값을 10, 내 스레드도 value값을 10을 의미하면 어떻게 할 것인가
결국 스케쥴러에 의해 정해져서 의미가 없다.
isAlive(): 활성화 상태인지 묻는 메소드. 작동 중이면 true, 아니면 false를 반환한다.(boolean값 반환)
1-7) 독립 스레드(Non Daemon)
Daed상태가 될 때까지 계속 실행되는 스레드
1-8) 종속 스레드(Daemon)
독립 스레드가 끝나면 같이 꺼지는 스레드
1-9) setDaemon: 종속 스레드로 설정
ThreadApp.java
package thread23;
//스레드로 구현하지 않은 클래스]
class NotThread{
String title;
public NotThread(String title) {
this.title = title;
}/////
//스레드로 구현하지 않은 메소드
void notThreadMethod() {
/*아래 로직이 시간이 오래 걸리는 작업이라고 가정*/
for(int i=1; i<=10;i++) {
System.out.println(String.format("%s]i=%d", title,i));
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {e.printStackTrace();}
}
}
}////////////////
//스레드로 구현한 클래스]
//1]Thread클래스를 상속받아 스레드 구현
class YesThread extends Thread{
//인자 생성자:매개변수(인자)로 스레드명으로 사용할 문자열을 받는다
public YesThread(String threadName) {
super(threadName);
}
void ThreadMethod() {
/*아래 로직이 시간이 오래 걸리는 작업이라고 가정*/
for(int i=1; i<=10;i++) {
System.out.println(String.format("%s]i=%d", getName(),i));
try {
//sleep(천분의 1초);
//스레드를 1000분 1초동안 wait상태로 빠지게 하는 메소드.
//1000분 1초후에는 다시 Runnable상태로 자동으로 돌아감.
sleep(1000);
}
catch (InterruptedException e) {e.printStackTrace();}
}
}
/*
* 2]run()메소드 오버라이딩
* -run()메소드 안에 시간이 오래 걸리는 작업 기술
* -개발자가 직접 호출못하고 스래드가
* Running상태로 들어갔을때 자동으로 호출되는 메소드(콜백 메소드)
*/
@Override
public void run() {
ThreadMethod();
}///////////////////
}
/*
독립 스레드(Non Daemon 스레드):
메인스레드와 working스레드(개발자가 만든 스레드)
메인스레드가 끝나도 종료되지 않고 스레드가
Dead상태 될때까지 계속 실행되는 스레드
종속 스레드(Daemon 스레드):
모든 독립스레드가 끝나면 자동으로 종료(Dead)가 되는
스레드
-주 스레드의 보조역할을 하는 스레드
-종속 스레드는 주로 무한루프로 구성한다.
-예]배경음악 깐다든지,10분마다 자동 저장한다든지 등등..
어떤 스레드를 종속 스레드로 만들려면
setDaemon(true)로 설정
*/
//종속 스레드로 사용할 스레드]
//1]Thread 클래스 상속
class DaemonThread extends Thread{
//2]run 메소드 오버라이딩
@Override
public void run() {
while(true){
System.out.println(String.format("%s]배경 음악이 흘려요", getName()));
System.out.println(String.format("%s]3초마다 저장", getName()));
try {
sleep(3000);
}
catch (InterruptedException e) {e.printStackTrace();}
}
}/////////
}//////////////
public class ThreadApp {
public static void main(String[] args) throws InterruptedException {
System.out.println("main 스레드 시작");
/*
//스레드로 구현하지 않은 클래스 테스트]
NotThread nt1=new NotThread("1st 클래스");
NotThread nt2=new NotThread("2nd 클래스");
nt1.notThreadMethod();
nt2.notThreadMethod();*/
//스레드로 구현한 클래스 테스트]
YesThread yt1 = new YesThread("1st 스레드");
YesThread yt2 = new YesThread("2st 스레드");
yt1.setName("첫번째 스레드");
yt1.start();//스레드를 Runnable상태로 전이시킴.run()메소드안의 코드가 실행되는것은 아님
//이건 start()하고 나서
//yt1.join();
/*
-join() 메소드
1]start()호출후에 join()메소드를 호출해야 한다
2]join()메소드를 호출한 스레드가
다 실행이 끝나야(Dead상태) 다른 스레드가 동작한다.
*/
/*
스레드의 우선권 설정]
우선순위가 높다고 그 스레드가
먼저 실행된다는 보장이 없다.(확률만 높을뿐)
*/
yt2.setPriority(Thread.MAX_PRIORITY);
yt2.start();//runnable상태에 빠짐
DaemonThread daemonThread = new DaemonThread();
daemonThread.setName("Daemon Thread");
daemonThread.setDaemon(true);//종속 스레드 설정
daemonThread.start();
System.out.println("현재 활성화 상태(Runnable 혹은 Running상태)에 있는 스레드 수:"+Thread.activeCount());
System.out.println("첫번쨰 스레드 우선권:"+yt1.getPriority());//디폴트는 5 max는 10 min은 1의 값을 가짐
System.out.println("두번쨰 스레드 우선권:"+yt2.getPriority());
System.out.println("현재 실행중(Running)중인 스레드:"+Thread.currentThread().getName());
System.out.println("main스레드의 우선권:"+Thread.currentThread().getPriority());
System.out.println("main 스레드 끝");
}///////main
}//////////class
2. Runnable인터페이스
Runnable인터페이스를 상속받으면 implement로 2개 상속받음
run()메소드 오버라이딩 해야 됨.
commander가 Thread를 상속받지 못했는데
commander.start()는 start()메소드는 Thread에 있어서 사용불가
해결방법:
Runnable을 Thread타입으로 변환하고 싶어도 상속관계가 없어서 형변환 안된다
우회방법으로 Thread클래스의 인자 생성자를 이용해서
Runnable을 상속받은 Commander의 객체(command)를 Thread타입으로 변환
ex) Thread th1 = new Thread(target); 로 생성
RunnableApp.java
package thread23;
class Soldier {
void longedMethod() {
for (int i = 1; i < 10; i++) {
System.out.println(String.format("스레드명:%s,i=%d", Thread.currentThread().getName(), i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}////////////
//상속 받은 longMEthod()스레드로 구현하자
//1]Runnable인터페이스 상속
class Commander extends Soldier implements Runnable {
//2]run()오버라이딩
@Override
public void run() {
longedMethod();
}
}
public class RunnableApp {
public static void main(String[] args) {
Commander commander = new Commander();
System.out.println(commander instanceof Commander);
System.out.println(commander instanceof Soldier);
System.out.println(commander instanceof Runnable);
//System.out.println(commander instanceof Thread);
//commander.start();//[x]
//Runnable타입을 Thread타입으로 변환.(상속관계가 없어서 형변환 불가)
//Thread클래스의 인자 생성자를 이용해서 Thread타입으로 변환
Thread th1=new Thread(commander);
th1.start();
Thread th2=new Thread(commander,"두번째 스레드");
th2.start();
}//////// main
}///////// class
sol)
3. 동기화(Synchronized)
정의: 공유 메모리를 여러 스레드가 동시에 사용하지 못하도록 하는 것 lock을 건다라고 볼 수 있음.
하나의 스레드만이 공유 메모리를 참조하도록 제한하는 방법
synchronized는 modifier이다
3-1) 메소드 동기화
여러 스레드에 의해 호출되는 공유 메소드를 동기화 함으로써 여러 스레드가 동시에 호출 못하도록 lock하는 것
여러 스레드가 공유하는 메소드를 가진 클래스와 인스턴스를 하나만 만든다.
그리고 각 스레드에 인스턴스를 전달
package thread23;
/*
[형식]
접근지정자 synchronized 반환타입 메소드명([매개변수]){
}
*/
class MethodSyncClass {
int seed;
public MethodSyncClass(int seed) {
super();
this.seed = seed;
}////////
//동기화시 synchronize 지정자 메소드에 추가]
synchronized void increase(int inc) {
for(int i=1;i<=10;i++) {
seed+=inc;
System.out.println(String .format("[스레드명:%s,데이타:%s,i=%s]",
Thread.currentThread().getName(),seed,i));
try {Thread.sleep(1000);}
catch(InterruptedException e) {e.printStackTrace();}
}
}/////////
}/////////////
//[공유 메소드를 호출하는 스레드]
class MethodSyncThread extends Thread{
//[멤버 변수]
MethodSyncClass msc;
//일정하게 증가시킬 숫자를 저장할 멤버(스레드마다 다르게 주자)
int inc;
//[인자 생성자]
public MethodSyncThread(MethodSyncClass msc, int inc, String threadName) {
super(threadName);
this.msc = msc;
this.inc = inc;
}////////
@Override
public void run() {
msc.increase(inc);
}///////
}//////
public class MethodSynchronized {
public static void main(String[] args) {
//공유 메소드 갖고 있는 클래스,하나만 인스턴스화]
MethodSyncClass msc= new MethodSyncClass(10);
//두 개의 스레드 생성]
MethodSyncThread mst1 = new MethodSyncThread(msc,2,"1st 스레드");
mst1.start();
MethodSyncThread mst2 = new MethodSyncThread(msc,5,"2nd 스레드");
mst2.start();
}//////// main
}//////// class
sol)
3-2) 데이터 동기화
동기화 안 한 결과: 동기화를 하지 않으면 각각의 스레드가 공유 데이터를 번갈아 가면서 증가시킨다
동기화한 결과: 1st스레드가 2씩 증가시킨 결과가 끝난 후, 2nd가 시작, 5씩 증가한 결과가 나타남
DataSynchronized.java
package thread23;
//동기화 블락을 이용한 데이타 동기화
//동기화 블락:
/*
synchronized(공유 메모리를 갖고 있는 객체){
동기화할 로직
}
*/
/* 여러 스레드가 공유하는 데이타(메모리)를 갖고 있는 클래스-인스턴스 하나만 만든다
각 스레드에 인스턴스를 전달한다 */
class DataSyncClass{
int shareData;//여러 스레드가 공유하는 메모리
public DataSyncClass(int shareData) {
this.shareData = shareData;
}
}///////////////////////////
/* 공유 데이타를 사용하는 스레드 */
class DataSyncThread extends Thread{
//[멤버변수]
//공유할 데이타를 갖고 있는 DataSynClass타입의 멤버
DataSyncClass dsc;
//일정하게 증가시킬 숫자를 저장할 멤버
int inc;
//[인자 생성자]
public DataSyncThread(DataSyncClass dsc, int inc,String threadName) {
super(threadName);
this.dsc = dsc;
this.inc = inc;
}
//[오래 걸리는 메소드]
//DataSynClass의 shareData에 저장된 값을 반복하면서
//inc(증가분)만큼 누적해서 계속 저장.
void increase() {
for(int i=1;i<=10;i++) {
dsc.shareData+=inc;
System.out.println(String.format("[스레드명:%s,공유 데이타:%s]",
getName(),dsc.shareData));
try {sleep(1000);}
catch (InterruptedException e) {e.printStackTrace();}
}
}//////////////////
@Override
public void run() {
//데이타 동기화 전]
//increase();
//데이타 동기화 후]
synchronized (dsc) {
increase();
}
}
}/////////////////////////////
public class DataSynchronized {
public static void main(String[] args) {
//공유 메모리 갖고 있는 클래스, 하나만 인스턴스화]
DataSyncClass dsc = new DataSyncClass(10);
//두 개의 스레드 생성]
DataSyncThread dst1 = new DataSyncThread(dsc,2, "1st 스레드");
dst1.start();
DataSyncThread dst2= new DataSyncThread(dsc,5, "2nd 스레드");
dst2.start();
}///////////////main
}//////////////////class
기타) 음악 실행 코드
MediaPlayer.java
package thread23;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import javazoom.jl.player.Player;
/*
선행작업 :1.http://www.java2s.com/Code/Jar/j/Downloadjlayer101jar.htm에서
Download jlayer-1.0.1-1.jar클릭하여 다운로드
2.lib폴더 만든후 다운로드한 jar붙여넣기
프로젝트 선택후 마우스 우클릭->Build Path->Configure Build Path->
Libraries탭에서 Classpath선택후->Add External Jars후 lib폴더에 있는
라이브러리 선택하여 클래스패스에 추가
3.music폴더 만들어 mp3파일 저장
*/
public class MediaPlayer extends Thread {
private Player player;
private FileInputStream fis;
String filePath;
public MediaPlayer(String filePath) {
this.filePath=filePath;
}
public void play() {
try {
fis = new FileInputStream(filePath);
player = new Player(fis);
player.play();
}
catch (Exception ex) {
System.out.println("Error: " + ex.getMessage());
}
}
public void playStop(){
if (player != null) {
player.close();
player = null;
}
}
@Override
public void run() {
play();
}
}
MediaPlayerApp.java
package thread23;
import java.io.FileInputStream;
import java.util.Scanner;
import javazoom.jl.player.Player;
public class MediaPlayerApp {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
MediaPlayer player=null;
while(true) {
System.out.println("1.음악 재생 2. 음악 중지 3. 프로그램 종료");
System.out.println("메뉴번호를 입력하세요?");
int menu=Integer.parseInt(sc.nextLine().trim());
if(menu==1) {
player = new MediaPlayer("music/music.mp3");
player.start();
}
else if(menu==2) {
if(player !=null) player.playStop();
}
else if(menu==3) System.exit(0);
else System.out.println("잘못 입력하셨습니다");
}
}///////////////
}
sol)