Java线程各个阶段的状态:
那么我们开始一点点研究吧:
启动线程
一、定义线程
1、扩展java.lang.Thread类。
此类中有个run()方法,应该注意其用法:
public void run()
- 如果该线程是使用独立的
Runnable
运行对象构造的,则调用该
Runnable
对象的run
方法;否则,该方法不执行任何操作并返回。 -
Thread
的子类应该重写该方法。
2、实现java.lang.Runnable接口。
void run()
- 使用实现接口
Runnable
的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的
run
方法。 -
方法run
的常规协定是,它可能执行任何所需的操作。
二、实例化线程
1、如果是扩展java.lang.Thread类的线程,则直接new即可。
2、如果是实现了java.lang.Runnable接口的类,则用Thread的构造方法:
Thread(Runnable target)
Thread(Runnable target, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
三、启动线程
在线程的Thread对象上调用start()方法,而不是run()或者别的方法。
在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。
在调用start()方法之后:发生了一系列复杂的事情
启动新的执行线程(具有新的调用栈);
该线程从新状态转移到可运行状态;
当该线程获得机会执行时,其目标run()方法将运行。
注意:对Java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法是合法的。但并不启动新的线程。
三、启动线程
在线程的Thread对象上调用start()方法,而不是run()或者别的方法。
在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。
在调用start()方法之后:发生了一系列复杂的事情
启动新的执行线程(具有新的调用栈);
该线程从新状态转移到可运行状态;
当该线程获得机会执行时,其目标run()方法将运行。(如果不明白,仔细看一下图吧!)
注意:对Java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法是合法的。但并不启动新的线程。
实例:
通过继承Thread类实现线程
package com.haixu.thread; /* * 线程练习 * 通过继承Thread类来创建线程类 * */ public class FirstThread extends Thread{ private int i; //重写run()方法,run()方法的防反弹就是线程的执行体 public void run(){ for(;i < 5 ; i++){ //当线程类继承Thread类时,直接使用this即可获得当前的线程 //Thread对象的getName()返回当前线程的名字 //因此可以直接调用getName()方法返回当前线程的名字 System.out.println( getName() + " " + i); } } public static void main(String[] args) { for(int i = 0; i<4; i++){ System.out.println(Thread.currentThread().getName() + " " + i); if(i == 3){ //创建第一个线程 new FirstThread().start(); //创建第二个线程 new FirstThread().start(); } } } }
运行结果:
main 0 main 1 main 2 main 3 Thread-0 0 Thread-0 1 Thread-0 2 Thread-0 3 Thread-0 4 Thread-1 0 Thread-1 1 Thread-1 2 Thread-1 3 Thread-1 4
通过实现Runnable接口来创建线程类
package com.haixu.thread; /* * 线程学习 * 通过实现Runnable接口来创建线程类 * */ public class SecondThread implements Runnable{ private int i; //run()方法同样是线程的执行体 public void run(){ for(; i<5; i++){ //当线程类实现了Runnable接口时 //如果想获取当前线程,只能通过调用Threand.currentThread()方法 System.out.println(Thread.currentThread().getName() + " " + i); } } public static void main(String[] args) { for(int i = 0 ; i<4; i++){ System.out.println(Thread.currentThread().getName() + " " + i); if(i==3){ SecondThread st = new SecondThread(); //通过new Thread(target,name)方法创建新线程 new Thread(st , "线程1").start(); new Thread(st , "线程2").start(); } } } }
运行结果:
main 0 main 1 main 2 main 3 线程1 0 线程1 1 线程2 1 线程1 2 线程2 3 线程1 4
四、一些常见问题
1、线程的名字,一个运行中的线程总是有名字的,名字有两个来源,一个是虚拟机自己给的名字,一个是你自己的定的名字。在没有指定线程名字的情况下,虚拟机总会为线程指定名字,并且主线程的名字总是mian,非主线程的名字不确定。
2、线程都可以设置名字,也可以获取线程的名字,连主线程也不例外。
3、获取当前线程的对象的方法是:Thread.currentThread();此方法是Thread类的静态方法。
4、在上面的代码中,只能保证:每个线程都将启动,每个线程都将运行直到完成。一系列线程以某种顺序启动并不意味着将按该顺序执行。对于任何一组启动的线程来说,调度程序不能保证其执行次序,持续时间也无法保证。
5、当线程目标run()方法结束时该线程完成。
6、一旦线程启动,它就永远不能再重新启动。只有一个新的线程可以被启动,并且只能一次。一个可运行的线程或死线程可以被重新启动。
7、线程的调度是JVM的一部分,在一个CPU的机器上上,实际上一次只能运行一个线程。一次只有一个线程栈执行。JVM线程调度程序决定实际运行哪个处于可运行状态的线程。众多可运行线程中的某一个会被选中做为当前线程。可运行线程被选择运行的顺序是没有保障的。
8、尽管通常采用队列形式,但这是没有保障的。队列形式是指当一个线程完成“一轮”时,它移到可运行队列的尾部等待,直到它最终排队到该队列的前端为止,它才能被再次选中。事实上,我们把它称为可运行池而不是一个可运行队列,目的是帮助认识线程并不都是以某种有保障的顺序排列唱呢个一个队列的事实。
9、尽管我们没有无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。
线程状态的转换
线程状态
线程的状态转换是线程控制的基础。线程状态总的可分为五大状态:分别是生、死、可运行、运行、等待/阻塞。如下图:
1、新状态:线程对象已经创建,还没有在其上调用start()方法。
2、可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
3、运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
4、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。
5、死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
具体的实现过程请查看下一篇博文,具体讲述各个状态的实例