> Java > java지도 시간 > 본문

Java 재시작 가능 스레드 및 스레드 풀 클래스 설계(자세한 설명)

高洛峰
풀어 주다: 2017-01-23 16:18:05
원래의
1437명이 탐색했습니다.

JAVA 다중 스레드 프로그래밍을 아는 사람이라면 스레드를 생성하는 두 가지 방법이 있다는 것을 알고 있습니다. 하나는 클래스가 Thread 클래스를 직접 상속하고 해당 run() 메서드를 구현하는 것이고, 다른 하나는 클래스가 Runnable 인터페이스를 구현하는 것입니다. run() 메서드를 구현한 다음 이 클래스를 생성자 매개변수로 사용하여 다음 형식과 유사한 새 Thread를 만듭니다. Thread t=new Thread(myRunnable). 마지막으로 Thread 클래스의 start() 메소드를 실행하여 스레드를 시작합니다.

JAVA에서는 스레드 실행이 끝나면, 즉 run() 메서드를 실행한 후에는 다시 시작할 수 없습니다. 현재 이 스레드 개체는 쓸모 없는 개체가 되어 가비지 수집기의 재활용을 기다리고 있습니다. 다음에 이 스레드를 다시 시작하려면 새 스레드 개체를 만들고 다시 시작해야 합니다. 객체의 빈번한 생성과 소멸은 운영 효율성에 영향을 줄 뿐만 아니라 쓸모없는 스레드 객체는 재활용할 시간이 없기 때문에 대량의 가비지 메모리를 생성할 수도 있습니다. 이러한 영향은 저장 공간과 처리 속도가 상대적으로 제한된 모바일 플랫폼에서 특히 중요합니다. . 그렇다면 객체를 자주 생성하고 파괴하지 않고도 반복적으로 시작할 수 있도록 스레드 클래스를 다시 설계할 수 있습니까?

물론이죠. 다음으로 이 "재시작 가능한 스레드" 클래스의 디자인을 소개하겠습니다.

우선, 여전히 스레드가 수행하길 원하는 작업을 스레드의 run() 메소드에 직접 넣으면 어차피 목적을 달성할 수 없다는 점을 분명히 해야 합니다. 위에서 언급한 것처럼 JAVA 스레드 클래스는 run() 메서드를 실행한 후에는 다시 시작할 수 없습니다. 따라서 가능한 유일한 방법은 사용자 프로그램("사용자 프로세스"라고도 함)의 run() 메서드를 스레드의 실제 run() 메서드 내부에 있는 while 루프 본문에 넣는 것입니다. 기다리다 . 스레드를 다시 시작하기 위해 재시작 메서드가 호출되면 실제로 다음 while 루프를 시작하기 위해 대기 중인 스레드를 깨웁니다. 대략적인 생각은 정해져 있고, 다음 코드는 이해하기 쉽습니다.

public class ReusableThread implements Runnable {
 //线程状态监听者接口
 public interface ThreadStateListener {
  public abstract void onRunOver(ReusableThread thread);//当用户过程执行完毕后调用的方法
 }
  
 public static final byte STATE_READY=0; //线程已准备好,等待开始用户过程
 public static final byte STATE_STARTED=1; //用户过程已启动
 public static final byte STATE_DESTROYED=2; //线程最终销毁
  
 byte mState; //标示可重启线程的当前状态
  
 Thread mThread; //实际的主线程对象
 Runnable mProc; //用户过程的run()方法定义在mProc中
 ThreadStateListener mListener; //状态监听者,可以为null
  
 /** Creates a new instance of ReusableThread */
 public ReusableThread(Runnable proc) {
  mProc = proc;
  mListener = null;
  mThread = new Thread(this);
  mState = STATE_READY;
 }
  
 public byte getState() {return mState;}
  
 public void setStateListener(ThreadStateListener listener) {
  mListener = listener;
 }
  
 /**可以在处于等待状态时调用该方法重设用户过程*/
 public synchronized boolean setProcedure(Runnable proc) {
  if (mState == STATE_READY) {
   mProc = proc;
   return true;
  }
  else
   return false;
 }
  
 /**开始执行用户过程*/
 public synchronized boolean start() {
  if (mState == STATE_READY) {
   mState = STATE_STARTED;
   if (!mThread.isAlive()) mThread.start();
   notify(); //唤醒因用户过程执行结束而进入等待中的主线程
   return true;
  }
  else
   return false;
 }
  
 /**结束整个线程,销毁主线程对象。之后将不可再次启动*/
 public synchronized void destroy() {
  mState = STATE_DESTROYED;
  notify();
  mThread = null;
 }
  
 public void run() {
  while (true) {
   synchronized (this) {
    try {
     while (mState != STATE_STARTED) {
      if (mState == STATE_DESTROYED) return;
      wait();
     }
    } catch(Exception e) {e.printStackTrace();}
   }
    
   if (mProc != null) mProc.run();
   if (mListener != null) mListener.onRunOver(this); //当用户过程结束后,执行监听者的onRunOver方法
    
   synchronized (this) {
    if (mState == STATE_DESTROYED) return;
    mState = STATE_READY;
   }
  }
 }
  
}
로그인 후 복사

코드는 이해하기 쉽죠? 하지만 "상태 수신기" 인터페이스가 있는 이유를 설명하세요. 때로는 추가 처리를 수행할 수 있도록 사용자 프로세스가 종료된 후 적시에 알림을 받고 싶을 수도 있습니다. 이 경우 상태 리스너의 onRunOver 메서드가 유용합니다. 직관적인 예는 아래에 언급된 "스레드 풀" 클래스에서 사용자 프로세스를 실행한 후 다시 시작 가능한 스레드가 자동으로 풀로 반환되어야 한다는 것입니다. 이때 풀로 반환하는 작업은 onRunOver 메서드에 배치할 수 있습니다. , 해당 매개변수는 재시작 가능한 스레드 개체이므로 매개변수가 나타내는 개체는 스레드 풀로 재활용될 수 있습니다.

스레드 풀 클래스는 실제로 앞서 언급한 개체 풀 클래스의 하위 클래스이며, 그 안의 모든 개체는 ReusableThread 클래스에 속합니다. 또한 ReusableThread.ThreadStateListener 인터페이스를 구현하여 사용자 프로세스가 종료되면 적시에 알림을 받고 스레드를 재활용하는 작업을 수행할 수 있습니다.

public class ThreadPool extends ObjectPool implements ReusableThread.ThreadStateListener {
 public static final int DefaultNumThreads = 16; //默认池容量
  
 public ReusableThread getThread() {
  return (ReusableThread)fetch();
 }
  
 public void onRunOver(ReusableThread thread) {
  recycle(thread); //当用户过程结束时,回收线程
 }
  
 private void init(int size) {
  ReusableThread thread;
  //初始化线程池内容
  for (int i=0; i<size; i++) {
   thread = new ReusableThread(null);
   thread.setStateListener(this);
   setElementAt(thread, i);
  }
 }
  
 public ThreadPool(int size) {
  super(size);
  init(size);
 }
  
 public ThreadPool() {
  super(DefaultNumThreads);
  init(DefaultNumThreads);
 }
  
}
로그인 후 복사

물론 필요한 몇 가지 기능이 있습니다. 스레드에는 다시 시작할 수 있는 추가 "향상된" 스레드 클래스가 있으므로 스레드의 sleep()과 같은 원래 Thread 클래스의 기능도 사용할 수 있어야 합니다. 그러나 이는 상대적으로 간단하므로 여기서는 생략하겠습니다.

아래 테스트 프로그램을 작성해 보세요. 저는 다음과 같이 진행할 계획입니다. 스레드 풀 클래스를 사용하는 대신 개체 풀 클래스와 재시작 가능한 스레드 클래스에 대해 공동 테스트를 수행합니다. 개체 풀의 개체가 속한 CharEmitter 클래스는 Runnable 인터페이스와 스레드를 구현합니다. 상태 리스너 인터페이스이며 스레드 풀 개체에 포함되지 않지만 독립적으로 사용되는 다시 시작 가능한 스레드 멤버 개체를 포함합니다. CharEmitter 클래스에 정의된 이 스레드의 사용자 프로세스가 종료되면 onRunOver 메서드는 이 CharEmitter 객체를 풀로 재활용하는 작업을 수행합니다. 이는 스레드 풀 클래스를 간접적으로 테스트하는 역할도 합니다. onRunOver에서 재활용 작업을 수행한다는 점이 개체 풀과 다른 점이기 때문입니다.

명확하게 하기 위해 코드로 직접 이동하는 것이 좋습니다.

TestThreadPool.java:

/**字符放射器*/
class CharEmitter implements Runnable, ReusableThread.ThreadStateListener {
 char c; //被发射的字符
 boolean[] isEmitting; //标示某字符是否正被发射(直接以字符对应的ASCII码作下标索引)
 
 ReusableThread thread; //可重启线程对象
 
 ObjectPool myHomePool; //为知道应把自己回收到哪里,需要保存一个到自己所在对象池的引用
 
 CharEmitter(ObjectPool container, boolean[] isCharEmitting) {
  isEmitting=isCharEmitting;
  myHomePool=container;
  thread=new ReusableThread(this); //新建可重启线程对象,设其用户过程为CharEmitter类自己定义的
 }
 
 /**开始“发射”字符*/
 public void emit(char ch) {
  //字符被要求只能是&#39;0&#39;到&#39;9&#39;之间的数字字符
  if (ch>=&#39;0&#39; && ch<=&#39;9&#39;) {
   c=ch;
  }
  else c=&#39; &#39;;
  
  thread.start(); //启动线程
 }
 
 public void run() {
  if (c==&#39; &#39;) return; //若不是数字字符直接结束
  //为便于观察,不同数字之前的空格数目不同,以便将其排在不同列上
  int spaceLen=c-&#39;0&#39;;
  StringBuffer s=new StringBuffer(spaceLen+1);
  for (int i=0; i<spaceLen; i++) s.append(&#39; &#39;);
  s.append(c);
  
  while (isEmitting[c]) {
    System.out.println(s); //不断地向屏幕写字符
  }
 }
 
/**实现线程状态监听者接口中的方法*/
 public void onRunOver(ReusableThread t) {
  myHomePool.recycle(this); //回收自身入池
 }
}
 
 
 
public class TestThreadPool {
 
public static void main(String[] args) {
 // TODO Auto-generated method stub
 //标示字符是否正被发射的标志变量数组
 boolean[] isEmitting=new boolean[256];
 for (int i=0; i<256; i++) isEmitting[i]=false;
  
 ObjectPool emitters=new ObjectPool(10); //新建对象池,容量为10
 for (int i=0; i<10; i++) {
 //用CharEmitter对象填满池子
 emitters.setElementAt(new CharEmitter(emitters, isEmitting), i);
 }
  
 byte[] c=new byte[1];
 CharEmitter emitter;
  
 while(true) {
 try {
 System.in.read(c); //从键盘读入一个字符,以回车键表示输入结束
 } catch(Exception e) {e.printStackTrace();}
  
 if (isEmitting[c[0]]) {
 isEmitting[c[0]]=false; //若字符正被发射,则结束其发射
 }
 else {
 isEmitting[c[0]]=true;
 emitter=(CharEmitter)emitters.fetch(); //向池中索取一个CharEmitter对象
 emitter.emit((char)c[0]); //发射用户输入的字符
 }
 }
}
 
}
로그인 후 복사

실행 후 키보드로 0에서 9 사이의 숫자를 입력하고 뒤로 자동차를 누르면 해당 번호가 화면에 계속 스크롤됩니다. 동일한 번호를 다시 입력하면 해당 번호가 더 이상 표시되지 않습니다. 여러 숫자가 동시에 전송되면 서로 다른 숫자가 인터리브되어 표시되는 것을 분명히 볼 수 있습니다. 이는 바로 스레드 간 가상 머신 스케줄링의 결과입니다. 실행 결과는 우리가 설계한 클래스 함수가 ​​완전히 정확하다는 것을 보여줍니다.

나중에 논의할 J2ME의 블루투스 통신을 위한 보조 클래스에서는 스레드 풀과 재시작 가능 스레드가 대체할 수 없는 역할을 한다는 것을 알 수 있습니다.

Java 재시작 가능 스레드 및 스레드 풀 클래스 설계에 대한 위 기사(자세한 설명)는 모두 편집자가 공유한 내용이므로 참고할 수 있기를 바라며, PHP를 지원해 주시길 바랍니다. 중국사이트.

Java 재시작 가능 스레드 및 스레드 풀 클래스 설계에 대한 더 많은 관련 기사(자세한 설명)를 보려면 PHP 중국어 웹사이트에 주목하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿