线程生命周期以及线程创建的三种方式

1. 线程生命周期

线程生命周期图
java线程的五种基本状态

  • 新建状态(New)
    当线程对象创建后,即进入新建状态,如:Thread t = new MyThread();

  • 就绪状态(Runnable)
    当调用线程对象的start()方法时,线程即进入就绪状态。处于就绪状态的线程只是说明此线程已经做好准备,随时等待CPU调度执行,并不是说执行了start()方法就立即执行。

  • 运行状态(Running)
    当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。

  • 阻塞状态(Blocked)
    处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。

  • 死亡状态
    线程执行完毕或者是异常退出,该线程结束生命周期。

阻塞状态分类

  • 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
  • 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程占用),它会进入到同步阻塞状态;
  • 其他阻塞:通过调用线程的sleep()或join()或发出I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

2. 线程状态控制

  • 线程休眠-sleep()
    sleep()会让当前的线程暂停一段时间,并进入阻塞状态,调用sleep并不会释放锁。

注意点:

  1. sleep()是静态方法,故不要用线程实例对象调用它,它睡眠的始终是当前正在运行的线程,而不是调用它的线程对象。
  2. 线程实际休眠时间会大于设定的时间。
  3. sleep()方法声明抛出InterruptedException,所以调用sleep()需要捕获异常。
  • 线程让步-yield()
    调用yield()方法之后,从运行状态转换到就绪状态,CPU从就绪状态队列中只会选择与该线程优先级相同或者是优先级更高的线程去执行。

    yield()方法不需要抛出异常。

  • 线程合并-join()
    线程合并就是将几个并发线程合并为一个单一线程执行,应用场景就是当一个线程的执行必须是要等到其他线程执行完毕之后才能执行。

  • 线程优先级设置-priority
    Thread类提供setPriority(int newPriority)和getPriority()方法设置和返回优先级。

  • 守护线程-Daemon
    守护线程是为其他非守护线程提供服务的,比如JVM中的垃圾回收线程就是守护线程。当所有的前台线程都进入死亡状态时,守护线程会自动死亡。

    调用Thead实例的setDaemon(true)方法可以将指定的线程设置为守护线程。

3. 线程创建方式

1.通过继承Thread类来创建并启动多线程的方式。
Java所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,run()方法即称为线程执行体。
  2. 创建Thread子类的实例。
  3. 调用线程对象的start()方法来启动该线程。

继承Thread类创建线程示例

public class MyThreadTest extends Thread {
    private int i;

    @Override
    public void run(){
        for (; i < 100; i++) {
            System.out.println(this.getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            // 调用Thread的currentThread方法获取当前线程
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 20) {
                // 创建、并启动第一条线程
                new MyThreadTest().start();
                // 创建、并启动第二条线程
                new MyThreadTest().start();
            }
        }
    }
}

2.通过实现Runnable接口来创建并启动线程的方式。
实现Runnable接口创建线程步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  3. 调用线程对象的start()方法来启动线程。

需要注意的是:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。

实现Runnable接口创建线程示例

public class MyRunnableTest implements Runnable {
    private int i;

    void print() {
        System.out.println(Thread.currentThread().getName() + " " + i);
    }

    @Override
    public void run() {
        for (; i < 100; i++) {
            print();
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 20) {
                MyRunnableTest st = new MyRunnableTest();
                // 通过new Thread(target, name)方法创建新线程
                // new Thread(st).start();亦可
                new Thread(st, "新线程-1").start();
                new Thread(st, "新线程-2").start();
            }
        }
    }
}

部分运行结果:
部分运行结果

从该运行结果中我们可以看出,控制台上输出的内容是乱序的,而且每次结果不尽相同。这是因为:

  1. 在这种方式下,程序所创建的Runnable对象只是线程的target,而多个线程共享同一个target,所以多个线程共享同一个线程类即线程的target类的实例属性。
  2. print()方法并不是线程安全的。

解决方案:对print()方法添加synchronized关键字保证线程安全。

synchronized void print() {
    System.out.println(Thread.currentThread().getName() + " " + i);
}

3.通过实现Callable接口来创建并启动线程的方式。
从Java 5开始,Java提供了Callable接口,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大。

  • call()方法可以有返回值;
  • call()方法可以声明抛出异常。

Java 5提供了Future接口来代表Callable接口call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口和Runnable接口可以作为Thread类的target。
注意Callable接口有泛型限制,Callable接口里的泛型形参类型与call()方法返回值类型相同。

创建并启动有返回值的线程的步骤如下:

  1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值。
  2. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了call()方法的返回值。
  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程。
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

注意:get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回。另外FutureTask还提供了get(long timeout, TimeUnit unit)方法,如果在指定时间内,还没获取到结果,就直接返回null。

实现Callable接口创建线程示例

public class MyCallableTest implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int i = 0;
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
        return i;
    }

    public static void main(String[] args) {
        // 创建Callable对象
        MyCallableTest myCallableTest = new MyCallableTest();
        // 使用FutureTask来包装Callable对象
        FutureTask<Integer> task = new FutureTask<Integer>(myCallableTest);
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 20) {
                // 实质还是以Callable对象来创建、并启动线程
                new Thread(task, "callable").start();
            }
        }
        try {
            System.out.println("callable返回值:" + task.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

总结:三种方式创建线程,继承Thread类,实现Runnable接口和实现Callable接口。实现Runnable接口和实现Callable接口的方式多个线程可以共享一个target对象,比较适用多个相同线程处理同一份资源的情况。

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值