이 글에서는 주로 Java 스레드의 라이프사이클과 상태 제어를 소개합니다. 필요한 친구는
을 참조하세요. 라이프 사이클
스레드 상태 전이 다이어그램:
1.
new 키워드와 Thread 클래스 또는 해당 하위 클래스를 사용하여 스레드 객체 를 생성한 후 스레드 객체는 새로운 상태가 됩니다. 새로운 상태의 스레드는 자체 메모리 공간을 가지며 start 메소드를 호출하여 준비 상태(실행 가능)로 들어갑니다.
참고: 이미 시작된 스레드에서는 start() 메서드를 다시 호출할 수 없습니다. 그렇지 않으면 Javalang.IllegalThreadStateException 예외가 발생합니다.
2. 준비 상태
준비 상태의 스레드는 실행 조건이 있지만 아직 CPU에 할당되지 않았습니다. thread is Ready 팁: start() 메서드 호출 후 즉시 하위 스레드를 실행하려면 Thread.sleep() 메서드를 사용하여 잠시 동안 메인 스레드를 절전 모드로 만든 후 하위 스레드를 실행하면 됩니다. -실.3. 실행 상태
실행 상태의 스레드는 가장 복잡하며 차단되거나 준비되거나 종료될 수 있습니다. 준비 상태의 스레드가 CPU에 의해 예약되면 준비 상태에서 실행 상태로 변경되고 run() 메서드에서 작업을 실행합니다. 스레드가 CPU 리소스를 잃으면 실행 상태에서 준비 상태로 변경됩니다. 시스템이 리소스를 다시 할당할 때까지 기다립니다. 실행 중인 스레드에서 Yield() 메서드를 호출할 수도 있습니다. 그러면 CPU 리소스를 포기하고 다시 준비 상태가 됩니다.다음 상황이 발생하면 스레드는 실행 상태에서 차단 상태로 변경됩니다.
①스레드는 sleep 메소드를 호출하여 적극적으로 포기합니다. 시스템 리소스를 점유했습니다②. 스레드가 차단 IO 메서드를 호출합니다. 메서드가 반환되기 전에 스레드가 차단됩니다3. 스레드가 동기화 모니터를 얻으려고 시도합니다. IV가 있고 스레드가 알림을 기다리고 있는 경우(
ify가 아님)
⑤가 있으면 프로그램이 suspend스레드 정지를 종료하는 스레드 메서드입니다. 그러나 이 방법은 쉽게 교착 상태로 이어질 수 있으므로 프로그램은 이 방법을 사용하지 않도록 노력해야 합니다. 스레드의 run() 메소드가 실행되거나 강제 종료되는 경우, 예를 들어 예외가 발생하거나 s
top(), desyory() 메소드 등이 호출되는 경우 , 실행 상태에서 정지 상태로 전환됩니다.
4. 차단 상태 sleep(sleep) 메서드를 실행하거나 대기하는 등 특정 상황에서 실행 중인 스레드 I/O 장치와 같은 리소스는 CPU를 포기하고 일시적으로 자체 작업을 중지하고 차단 상태에 들어갑니다.
차단된 상태의 스레드는 준비 대기열에 들어갈 수 없습니다. Sleep 시간이 만료되거나 대기 중인 I/O 장치가 유휴 상태인 등 차단 원인이 제거된 경우에만 스레드는 준비 상태로 진입하고 다시 준비 대기열에 대기한 후 원래 중지에서 시작합니다. 시스템에 의해 선택된 후 위치를 계속 실행합니다.5. 사망 상태 스레드의 run() 메소드가 실행되거나 강제 종료되면 죽은 것으로 간주됩니다. 이 스레드 개체는 활성 상태일 수 있지만 더 이상 별도로 실행되는 스레드가 아닙니다. 스레드가 한번 죽으면 다시 되살릴 수 없습니다. start() 메소드가 데드 스레드에서 호출되면 java.lang.IllegalThreadStateException 예외가 발생합니다.
2. 스레드 상태 제어 Java는 스레드 상태를 제어하는 몇 가지 편리한 방법을 제공합니다.
.
가능한 한 사용하지 않는 메소드가 많으며 start(), Interrupt(), Join()에 집중해야 합니다. , sleep(), Yield() 등의 직접 제어 방식과 setDaemon(), setPriority() 등의 간접 제어 방식이 있습니다.
1. Thread sleep - sleep
현재 실행 중인 스레드를 일정 시간 동안 일시 중지하고 차단 상태에 들어가야 하는 경우 , 그러면 Thread의 sleep 메소드를 호출할 수 있습니다. 위에서 볼 수 있듯이 sleep 메소드에는 overloading의 두 가지 형태가 있지만 사용 방법은 동일합니다.
예를 들어, 메인 스레드를 100밀리초마다 휴면 상태로 만든 다음 숫자를 인쇄하려고 합니다.
public class Test { public static void main(String[] args) throws InterruptedException { for(int i=;i<;i++){ System.out.println("main"+i); Thread.sleep(); } } }
인쇄된 내용을 확실히 볼 수 있습니다. 숫자는 시간상 상단에 약간의 간격이 있습니다.
다음 사항에 주의하세요
①. sleep은 정적 메서드로 호출하지 않는 것이 가장 좋습니다. 스레드는 휴면 상태이기 때문에 항상 현재 실행 중인 스레드이지 이를 호출하는 스레드 개체에 대해서만 유효합니다. 다음 예를 보십시오:
public class Test1 { public static void main(String[] args) throws InterruptedException { System.out.println(Thread.currentThread().getName()); MyThread myThread=new MyThread(); myThread.start(); myThread.sleep(1000);//这里sleep的就是main线程,而非myThread线程 Thread.sleep(10); for(int i=0;i<100;i++){ System.out.println("main"+i); } } }
② Java 스레드 스케줄링은 Java 멀티스레딩의 핵심입니다. 좋은 스케줄링만이 시스템 성능을 최대한 발휘할 수 있습니다. 프로그램의 실행 효율성을 향상시킵니다. 그러나 프로그래머가 어떻게 스케줄을 작성하더라도 스레드 실행 순서에만 최대한 영향을 미칠 수 있을 뿐 정밀한 제어는 불가능합니다. 슬립 메소드를 사용한 후에는 스레드가 차단 상태에 들어가고, 슬립 시간이 끝난 후에야 준비 상태에서 실행 상태로의 전환이 시스템에 의해 제어될 수 있습니다. 따라서 Thread.sleep(1000)을 호출하여 스레드를 1초 동안 절전 모드로 전환하면 결과가 1초보다 클 수 있습니다.
public class Test1 { public static void main(String[] args) throws InterruptedException { new MyThread().start(); new MyThread().start(); } } class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(this.getName()+"线程" + i + "次执行!"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } }
특정 작업의 결과를 살펴보세요.
1. Thread-0 스레드가 0번 실행되었습니다!
2. 스레드 1개가 0번 실행되었습니다!
3. 1개의 스레드가 한 번 실행됩니다!
4.-0 스레드는 한 번 실행됩니다!
5.-0 스레드가 두 번 실행되었습니다!
6.-1 스레드가 두 번 실행됩니다!
보시다시피 스레드 0이 먼저 실행된 다음 스레드 1이 한 번 실행되고 다시 실행됩니다. sleep 순서대로 실행되지 않는 것을 볼 수 있습니다.
2. 스레드 양보 - Yield
yield() 메소드는 sleep() 메소드와 다소 유사하며, 이것도 하나입니다. Thread 클래스의 Static 메서드에서 제공하는 이 메서드는 현재 실행 중인 스레드를 일시 중지하고 CPU 리소스를 다른 스레드에 포기할 수도 있습니다. 하지만 sleep() 메서드와 달리 차단 상태로 들어가지 않고 준비 상태로 들어갑니다. Yield() 메서드는 현재 스레드를 일시 중지하고 준비된 스레드 풀에 다시 들어가서 시스템의 스레드 스케줄러가 이를 다시 예약하도록 합니다. 스레드가 Yield() 메서드를 호출하면 다음과 같은 상황이 발생할 수 있습니다. 스레드 스케줄러는 이를 예약하고 실행을 위해 실행 상태로 다시 들어갑니다.
실제로 스레드가 일시 중지하기 위해 Yield() 메서드를 호출할 때 우선순위는 현재 스레드와 동일하거나 준비 상태의 스레드보다 우선순위가 더 높습니다. 현재 스레드가 더 많은 실행 기회를 얻는 것은 가능합니다. 물론 이는 CPU 스케줄링 스레드를 정확하게 방해할 수 없기 때문에 가능한 일입니다.
yield 사용법:
public class Test1 { public static void main(String[] args) throws InterruptedException { new MyThread("低级", 1).start(); new MyThread("中级", 5).start(); new MyThread("高级", 10).start(); } } class MyThread extends Thread { public MyThread(String name, int pro) { super(name);// 设置线程的名称 this.setPriority(pro);// 设置优先级 } @Override public void run() { for (int i = 0; i < 30; i++) { System.out.println(this.getName() + "线程第" + i + "次执行!"); if (i % 5 == 0) Thread.yield(); } } }
sleep() 메소드와 Yield() 메소드의 차이점은 다음과 같습니다.
① sleep 메서드가 현재 스레드를 일시 중단한 후 차단 상태로 진입합니다. sleep 시간이 다 된 후에만 준비 상태로 들어갑니다. Yield 메소드 호출 후 바로 Ready 상태로 진입하므로, Ready 상태 진입 직후에 Running 상태로 스케줄링될 수도 있다.
②. sleep 메소드는 InterruptedException이 발생한다고 선언하므로 sleep 메소드 호출 시 예외를 catch하거나 예외가 발생함을 명시적으로 선언해야 합니다. 항복 메소드는 작업 예외를 발생시킨다고 선언하지 않습니다.
3. sleep 방법은 항복 방법보다 이식성이 좋습니다. 일반적으로 동시 스레드 실행을 제어하기 위해 항복 방법에 의존하지 마십시오.
3. 스레드 병합 - 조인
스레드 병합의 의미는 여러 개의 병렬 스레드를 단일 스레드로 병합하여 실행하는 것입니다. 시나리오는 스레드가 실행되기 전에 다른 스레드가 실행을 완료할 때까지 기다려야 하는 경우입니다. Thread 클래스는 이 기능을 완료하기 위해 조인 메서드를 제공합니다.
위 메소드 목록에서 볼 수 있듯이 3개의 오버로드된 메소드가 있습니다:
void Join()
현재 스레드 등 이제 스레드에 참여하고 스레드가 종료될 때까지 기다릴 시간입니다.
void join(long millis)
当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度
void join(long millis,int nanos)
等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度
例子:
public class Test1 { public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); thread.join(1);//将主线程加入到子线程后面,不过如果子线程在1毫秒时间内没执行完,则主线程便不再等待它执行完,进入就绪状态,等待cpu调度 for(int i=0;i<30;i++){ System.out.println(Thread.currentThread().getName() + "线程第" + i + "次执行!"); } } } class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println(this.getName() + "线程第" + i + "次执行!"); } } }
在这个例子中,在主线程中调用thread.join(); 就是将主线程加入到thread子线程后面等待执行。不过有时间限制,为1毫秒。
4、线程的优先级
每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级。
Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法的参数是一个整数,范围是1~•0之间,也可以使用Thread类提供的三个静态常量:
MAX_PRIORITY =10
MIN_PRIORITY =1
NORM_PRIORITY =5
例子:
public class Test1 { public static void main(String[] args) throws InterruptedException { new MyThread("高级", 10).start(); new MyThread("低级", 1).start(); } } class MyThread extends Thread { public MyThread(String name,int pro) { super(name);//设置线程的名称 setPriority(pro);//设置线程的优先级 } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(this.getName() + "线程第" + i + "次执行!"); } } }
从结果可以看到 ,一般情况下,高级线程更显执行完毕。
注意一点:虽然Java提供了10个优先级别,但这些优先级别需要操作系统的支持。不同的操作系统的优先级并不相同,而且也不能很好的和Java的10个优先级别对应。所以我们应该使用MAX_PRIORITY、MIN_PRIORITY和NORM_PRIORITY三个静态常量来设定优先级,这样才能保证程序最好的可移植性。
5、守护线程
守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。
守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。
setDaemon方法的详细说明:
public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。 该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
参数:
on - 如果为 true,则将该线程标记为守护线程。
抛出:
IllegalThreadStateException - 如果该线程处于活动状态。
SecurityException - 如果当前线程无法修改该线程。
/** * Java线程:线程的调度-守护线程 */ public class Test { public static void main(String[] args) { Thread t1 = new MyCommon(); Thread t2 = new Thread(new MyDaemon()); t2.setDaemon(true); //设置为守护线程 t2.start(); t1.start(); } } class MyCommon extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("线程1第" + i + "次执行!"); try { Thread.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyDaemon implements Runnable { public void run() { for (long i = 0; i < 9999999L; i++) { System.out.println("后台线程第" + i + "次执行!"); try { Thread.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } } } }
执行结果:
1. 后台线程第0次执行!
2. 线程1第0次执行!
3. 线程1第1次执行!
4. 后台线程第1次执行!
5. 后台线程第2次执行!
6. 线程1第2次执行!
7. 线程1第3次执行!
8. 后台线程第3次执行!
9. 线程1第4次执行!
10. 后台线程第4次执行!
11. 后台线程第5次执行!
12. 后台线程第6次执行!
13. 后台线程第7次执行!
从上面的执行结果可以看出:前台线程是保证执行完毕的,后台线程还没有执行完毕就退出了。
实际上:JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台县城时候一定要注意这个问题。
守护线程的用途:
守护线程通常用于执行一些后台作业,例如在你的应用程序运行时播放背景音乐,在文字编辑器里做自动语法检查、自动保存等功能。Java的垃圾回收也是一个守护线程。守护线的好处就是你不需要关心它的结束问题。例如你在你的应用程序运行的时候希望播放背景音乐,如果将这个播放背景音乐的线程设定为非守护线程,那么在用户请求退出的时候,不仅要退出主线程,还要通知播放背景音乐的线程退出;如果设定为守护线程则不需要了。
6、如何结束一个线程
Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit这些终止线程运行的方法已经被废弃了,使用它们是极端不安全的!想要安全有效的结束一个线程,可以使用下面的方法。
1、正常执行完run方法,然后结束掉
2、控制循环条件和判断条件的标识符来结束掉线程
比如说run方法这样写:
class MyThread extends Thread { int i=0; @Override public void run() { while (true) { if(i==10) break; i++; System.out.println(i); } } }
或者
class MyThread extends Thread { int i=0; boolean next=true; @Override public void run() { while (next) { if(i==10) next=false; i++; System.out.println(i); } } }
或者
class MyThread extends Thread { int i=0; @Override public void run() { while (true) { if(i==10) return; i++; System.out.println(i); } } }
只要保证在一定的情况下,run方法能够执行完毕即可。而不是while(true)的无线循环。
3、使用interrupt结束一个线程。
诚然,使用第2中方法的标识符来结束一个线程,是一个不错的方法,但是如果,该线程是处于sleep、wait、join的状态的时候,while循环就不会执行,那么我们的标识符就无用武之地了,当然也不能再通过它来结束处于这3种状态的线程了。
可以使用interrupt这个巧妙的方式结束掉这个线程。
我们看看sleep、wait、join方法的声明:
public final void wait() throws InterruptedException public static native void sleep(long millis) throws InterruptedException public final void join() throws InterruptedException
可以看到,这三者有一个共同点,都抛出了一个InterruptedException的异常。
在什么时候会产生这样一个异常呢?
每个Thread都有一个中断状状态,默认为false。可以通过Thread对象的isInterrupted()方法来判断该线程的中断状态。可以通过Thread对象的interrupt()方法将中断状态设置为true。
当一个线程处于sleep、wait、join这三种状态之一的时候,如果此时他的中断状态为true,那么它就会抛出一个InterruptedException的异常,并将中断状态重新设置为false。
看下面的简单的例子:
public class Test1 { public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); } } class MyThread extends Thread { int i=1; @Override public void run() { while (true) { System.out.println(i); System.out.println(this.isInterrupted()); try { System.out.println("我马上去sleep了"); Thread.sleep(2000); this.interrupt(); } catch (InterruptedException e) { System.out.println("异常捕获了"+this.isInterrupted()); return; } i++; } } }
测试结果:
1. 1
2. false
3. 我马上去sleep了
4. 2
5. true
6. 我马上去sleep了
7. 异常捕获了false
可以看到,首先执行第一次while循环,在第一次循环中,睡眠2秒,然后将中断状态设置为true。当进入到第二次循环的时候,中断状态就是第一次设置的true,当它再次进入sleep的时候,马上就抛出了InterruptedException异常,然后被我们捕获了。然后中断状态又被重新自动设置为false了(从最后一条输出可以看出来)。
所以,我们可以使用interrupt方法结束一个线程。具体使用如下:
public class Test1 { public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); Thread.sleep(3000); thread.interrupt(); } } class MyThread extends Thread { int i=0; @Override public void run() { while (true) { System.out.println(i); try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("中断异常被捕获了"); return; } i++; } } }
多测试几次,会发现一般有两种执行结果:
0
1
2
4. 中断异常被捕获了
或者
0
1
2
3
5. 中断异常被捕获了
这两种结果恰恰说明了 只要一个线程的中断状态一旦为true,只要它进入sleep等状态,或者处于sleep状态,立马回抛出InterruptedException异常。
第一种情况,是当主线程从3秒睡眠状态醒来之后,调用了子线程的interrupt方法,此时子线程正处于sleep状态,立马抛出InterruptedException异常。
첫 번째 상황은 메인 스레드가 3초 슬립 상태에서 깨어나 서브 스레드의 인터럽트 메소드를 호출하는 경우입니다. 이때 서브 스레드는 아직 슬립 상태가 아닙니다. 그런 다음 세 번째 while 루프 동안 절전 상태로 전환되고 즉시 InterruptedException이 발생합니다.
위 내용은 Java 스레드의 수명 주기 및 상태 제어에 대한 자세한 그래픽 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!