- Java使用Thread类代表线程,所有的线程对象都必须是Tread类或其子类的实例。每条线程的作用是完成一定的任务,实际上就是执行一段程序流。Java使用run方法来封装这样的一段程序流。
通过继承Thread类来创建并启动多线程的步骤如下:
- 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就是代表了线程需要完成的任务。因此我们经常把run方法称为线程执行体。
- 创建Thread子类的实例,即使创建了线程对象
- 用线程对象的start方法来启动该线程
public class FirstThread extends Thread{
private int i;
public void run(){
for(;i < 100; i ++){
//当线程类继承Thread类时,可以直接调用getName()方法来返回当前线程的名字
//如果想获取当前线程,直接使用this即可
//Thread对象的getName()返回该线程的名字
System.out.println(getName() + "" + i);
}
}
public static void main(){
for(int i = 0; I < 100; i++){
System.out.println(Thread.currentThread().getName() + "" + i);
if (i == 20){
new FirstTread().start();
new FirstTread().start();
}
}
}
}
注意一:Thread-0 和Thread -1两条线程输出的i变量不连续,i变量是FirstThread的实例属性,而不是局部变量,但是因为程序每次创建线程对象时都需要创建一个FirstThread对象,所以不共享该实例属性。使用继承Thread
类的方法来创建线程类,多条线程之间无法共享线程类的实例变量。
注意二:启动线程应该使用start方法,而不是run方法。永远不要调用线程对象的run方法。调用start方法来启动线程,系统会把该run方法当成线程执行体来处理。但如果直接调用线程对象的run方法,系统指挥把线程对象当作一个普通的对象,而run方法也只是普通方法,而不是线程执行体。
package com.imooc;
public class MyThread extends Thread{
private int i;
public void run(){
for (; i < 100; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args){
for (int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
if (i ==20){
new MyThread().run();
new MyThread().run();
}
}
}
}
- 实现Runnable接口创建线程
创建Runnable对象作为线程对象的target。使用这种方法时,两条线程的i变量是连续的,因为两条线程使用的是同一个target,即同一个Runnable对象。
public class SecondThread implements Runnable {
private int i;
@Override
public void run() {
// TODO Auto-generated method stub
for(; i < 100; i ++){
System.out.println(Thread.currentThread().getName() + " " + i); //注意三
}
}
public static void main(String[] args){
for(int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 20){
SecondThread st = new SecondThread();
new Thread(st, "新线程1").start();
new Thread(st, "新线程2").start();
}
}
}
}
注意三:
使用Runnable接口时,必须使用Thread.currentThread.getName()
方法来查看当前线程的名字
- 线程的生命周期
- 新建:new关键字创建了一个线程后,该线程就处于新建状态。此时和其他Java对象一样,仅仅由虚拟机为其分配了内存,并初始化了成员变量的值。没有表现出任何线程的动态特征。
- 就绪:当线程对象调用了
start()
方法后,该线程就处于就绪状态,Java虚拟机会为其创建方法调用栈,程序计数器,但是此时还没有运行,只是代表着该线程可以运行了。至于何时运行,取决于JVM里线程调度器的调度。 - 运行:如果就绪的线程获得了CPU,开始执行
run
方法的线程执行体,此时线程处于运行状态 - 阻塞:
发生以下情况时,线程进入阻塞:
- 线程调用sleep方法主动放弃处理器资源
- 等待资源时
- 程序调用了线程的suspend方法将该线程挂起(容易导致死锁)
针对以上的情况,发生特定的情况将可以接触上面的阻塞,让该线程重新进入就绪状态:
- 调用的sleep方法经过了指定的时间
- 等待的资源已获得
- 处于挂起状态的线程被调用了resume()回复方法。
- 死亡:我们可以通过调用线程对象的
isAlive()
方法来检测线程是否死亡,当线程处于就绪,运行,阻塞时,该方法返回true;处于新建,死亡时,该方法返回false。注意,不要试图对一个已经死亡的线程调用start()
方法,否则会产生IllegalThreadStateException异常。
线程会以以下三种方式之一结束,结束后就处于死亡状态;
-
run()
方法执行完成,线程正常结束 - 线程抛出一个未捕获的Exception或Error
- 直接调用该线程的
stop()
来结束该线程-该方法容易导致死锁 - 控制线程
- join线程:当在某个程序执行流中调用A线程的
join()
方法时,调用线程将被阻塞,直到被join()
方法加入的A线程完成为止。 - 后台线程:这种线程在后台运行,他的任务是为其他的线程提供服务。如果所有的前台线程死亡,后台线程会自动死亡。调用Thread对象的
setDaemon(true)
方法可将指定线程设置成后台线程,但是注意,setDeamon(true)
必须在start()
方法前调用,否则IllegalThreadStateException异常。 - 线程睡眠sleep()方法:通过调用Thread类的静sleep方法,可以让当前正在执行的线程暂定指定的时间,并进入阻塞状态
Thread.sleep(1000);
,在之后,线程自动进入就绪状态。 - 线程让步yield方法:这个方法也是Thread提供的一个静态方法,它将正在执行的线程自动转入就绪状态(但不阻塞)。当某个线程调用了yield方法暂停后,线程调度器完全可能再次将其调用出来执行。但是,当某个线程调用了
yield()
方法暂停后,只有优先级大于、等于当前线程的就绪状态的线程才会获得执行的机会。 - 改变线程的优先级:`thread.setPriority(MAX_PRIORITY)