线程的创建
Posted wuqinglong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程的创建相关的知识,希望对你有一定的参考价值。
目录
概述
多线程能大幅度的提升CPU的使用率, 使得任务处理更加快速, 但是也并不是多线程一定高效, 线程的切换会涉及到CPU上下文切换, 上下文的切换会大幅度的拉低CPU的性能.
线程的五种状态
新建状态(New)
当线程对象被创建后, 即进入了新建状态. 如: Thread thread = new MyThread();
就绪状态(Runnable)
当调用线程对象的start()方法后, 线程随即进入了就绪状态. 处于就绪状态的线程, 只能说明此线程已经做好了准备, 随时等待CPU调度执行, 并不是调用了start()之后就开始执行了.
运行状态(Running)
当CPU调度处于就绪状态的线程时, 线程才得以真正的执行, 随即进入了运行状态. 就绪状态是进入运行状态的唯一入口, 线程要想进入运行状态, 必须处于就绪状态.
阻塞状态(Blocked)
处于运行状态的线程由于某种原因, 暂时放弃对CPU的使用权, 停止执行, 此时进入阻塞状态. 直到其进入就绪状态, 才有机会被CPU重新调度.
进入阻塞状态可能的原因:
- 线程调用sleep()方法, 使线程进入休眠状态.
- 线程调用wait()方法使线程挂起, 直到被notify()或notifyAll()通知.
- 线程等待IO的完成.
- 线程在等待同步锁的释放.
死亡状态(Dead)
线程执行完成后或者因为异常退出了run()方法, 则线程的生命周期结束了.
线程状态的切换
箭头(->)表示可以切换到后续状态
- 新建状态 -> 就绪状态
- 就绪状态 -> 运行状态
- 运行状态 -> 就绪状态/阻塞状态/死亡状态
- 阻塞状态 -> 就绪状态
- 死亡状态 ->
线程的创建
继承Thread类
通过继承Thread类, 重写run()方法可以实现线程的创建.
线程执行体就是run()方法中的内容.
public class MyThread extends Thread {
@Override
public void run() {
// do something...
}
}
public class Demo {
public static void main(String[] args) {
new MyThread().start();
}
}
创建MyThread对象, 然后调用start()方法即可启动线程. 虽然线程的执行体是run()方法中的内容, 但是不能直接使用new MyThread().run();
启动线程. 这种方式启动的话就是普通方法的调用, 并不会有线程的创建.
实现Runnable接口
通过实现接口, 并实现接口中的run()方法, 然后在创建Thread对象时作为参数传入即可实现线程de创建. 线程执行体也就是run()方法中的内容.
public class MyThread implements Runnable {
@Override
public void run() {
// do something...
}
}
public class Demo {
public static void main(String[] args) {
new Thread(new MyThread()).start();
}
}
区别
两种创建方法的区别就是一个继承类, 一个是实现接口, 根据Java的语法规范可知, 继承是单继承而实现可以多实现.
打开Thread的源码可知, Thread实现了Runnable接口, 继续打开Thread的run()看到
@Override
public void run() {
if (target != null) {
target.run();
}
}
这里的target即为第二种方式传入的new MyThread()
, 如果直接使用new Thread().start();
, 结果就是创建一个线程, 但是线程没有线程执行体(没有执行代码), 随即结束线程. 因为target为null.
使用Callable创建线程
这种方式与使用Runnable创建类似, 但是该方式更加强大, 主要体现在两方面: 1. Callable可以抛出异常; 2. Callable可以有返回值.
并且, Callable在获取返回值的时候(调用get()方法时)会等待线程执行完成.(肯定要等待线程执行完成才能拿到返回值.)
使用:
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// do something...
return 10;
}
}
public class Demo {
public static void main(String[] args) throws Exception {
// 实例化对象
MyCallable callable = new MyCallable();
// 使用FutureTask封装
FutureTask<Integer> task = new FutureTask<>(callable);
// 启动线程
new Thread(task).start();
// 获取返回值, get()方法会有异常抛出
Integer integer = task.get();
}
}
如果这时你对Thread对象也可以接受FutureTask对象有疑问的话? 那么你非常棒.
打开FutureTask的源码看到他实现了RunnableFuture接口, 而RunnableFuture接口又继承了Runnable接口, 所以Thread可以接受FutureTask对象.
这时你如果能想到既然FutureTask实现了Runnable接口, 那么它的run()是如何实现的有疑问的话? 你的求知欲很强. 看看源码就知道了, 也会知道为什么需要重写call().
我们本应该就带有很强的求知欲, 去探索底层实现. 不然和机器有什么区别呢.
以上是关于线程的创建的主要内容,如果未能解决你的问题,请参考以下文章