前言
因为这段时间主要在学习多线程和MySQL的一些知识,所以整理一下Java多线程的知识出来.学没学会,能输出算是检验的标准之一.温故而知新,才能有进步.
我也是在学习的过程中,难免有认识浅陋的地方.学习这些内容也是检验一下自己的理解.其中出现谬误和不足还请大家多多指正.我会第一时间修改.
线程闲谈(可跳过)
Java程序在执行的过程中都会开辟出一个线程来执行当前的任务.通过Thread.currentThread().getName()方法获取当前正在执行的线程的名字并进行打印,我们能够得到:main正在执行!
public class Demo1 {
public static void main(String[] args){
while(true){
System.out.println(Thread.currentThread().getName()+"正在执行!");
}
}
}
这个时候系统中只有主线程main正在执行,它会一直输出main正在执行!
.
这种运行环境就是我们所说的单线程运行环境.
那么如何创建一个新的线程协助我们执行任务呢?请继续!
创建线程的三种方式
继承Thread类
Java中有一句话:万物皆对象.那我们想要创建一个线程出来应该怎么做呢?
对! 创建线程对象!
既然是线程对象,总是要有些它自己独特之处,所以我们需要继承Thread类,来表示当前类是一个线程类.
public class CreateDemo1{
public static void main(String[] args) {
ThreadDemo1 threadDemo1 = new ThreadDemo1();
// start()方法可启动线程并使线程进入就绪状态
threadDemo1.start();
}
}
// 内部类
class ThreadDemo1 extends Thread{ }
运行结果:
Process finished with exit code 0
前面提到我们开启一个新的线程是希望它能够协助我们完成任务,可是这个线程为什么什么都没有做呢?
重写run()方法
在Thread类中有一个run()方法,这个就是我们希望子线程完成的任务.默认情况下,Runnable对象主要决定了了我们的子线程究竟要做什么,但是Thread无参构造状态下这个Runnable target为null,所以我们的子线程什么也没有做.所以我们重写这个方法,来让子线程做我们想要让它做的事情.Runnable对象的使用后面再进行介绍.
public class Thread implements Runnable {
/* What will be run. */
// Runnable 主要定义了我们的子线程究竟要做什么!
private Runnable target;
// ... 略去了部分代码
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
run()方法默认去执行Runnable接口实现中的run()方法,当重写之后就会自动调用并执行run().
现在我希望我的子线程能够告诉我它在运行:
public class CreateDemo1{
public static void main(String[] args) {
ThreadDemo1 threadDemo1 = new ThreadDemo1();
threadDemo1.start();
}
}
class ThreadDemo1 extends Thread{
@Override
public void run() {
System.out.println("我是main函数开辟的线程!");
}
}
OK!
我是main函数开辟的线程!
Process finished with exit code 0
实现Runnable接口
通过观察Thread类我们发现,它实现了Runnable接口,并且Thread类的内部存在一个Runnable接口变量 target,并且不重写run()方法的时候,默认就是调用这个对象的run()方法.那么我们猜想是不是存在一种Thread的构造函数让我们能够传递一个Runnable对象给Thread类,让他执行这个方法呢?
public class CreateDemo2 {
public static void main(String[] args) {
// ThreadDemo2 是 Runnable 接口的一个实现类 所以它自己不能调用 start() 方法
ThreadDemo2 threadDemo2 = new ThreadDemo2();
Thread theadObject = new Thread(threadDemo2);
theadObject.start();
}
}
class ThreadDemo2 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is Running!");
}
}
执行结果如下:
Thread-0 is Running!
Process finished with exit code 0
以下将看一部分Thread类的源码.不感兴趣的话,可以暂时跳过这部分.
public class Thread implements Runnable {
/* What will be run. */
private Runnable target;
// 无参构造方法调用init方法,我们将目光放在第二个参数上
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
// 有参构造方法调用init方法,我们将目光放在第二个参数上
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
// 第二个参数就是我们的 Runnable target 对象
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
// 果然在这里将我们的传入的 target进行了赋值 this.target = target;
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// ...
this.target = target;
// ...
}
// 因为我们传递了 target 对象,所以调用 run()方法的时候执行的就是我们传入的线程任务了!
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
源码部分结束
实现Callable接口
前两种实现方式都是通过run()方法实现的.通过run方法的定义可知,我们无法通过它获取到子线程执行的结果.我们在子线程中计算前十个数字的和:
public void run() {
// 因为方法没有返回值
int sum = 0;
for(int i = 1; i <= 10; ++i){
sum += i;
}
// 最终这个 sum 是无法被 main 线程获取的
}
现在有一个业务需求需要子线程计算前10个数字的和
然后将结果返回给主线程,我们应该怎么办呢?
答案:继承Callable接口,重写里面的call()方法.
call()方法是有返回值的.但是我们不能直接将Callable接口传递给Thread类,需要借助一个FutureTask 类帮助我们实现这个任务.通过它们两个的配合,我们可以拿到我们想要的结果.
public class CreateDemo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadDemo3 threadDemo3 = new ThreadDemo3();
FutureTask futureTask = new FutureTask(threadDemo3);
Thread threadObject = new Thread(futureTask);
threadObject.start();
Object res = futureTask.get();
System.out.println("前10个数字的和为:" + res);
}
}
class ThreadDemo3 implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for(int i = 1; i <= 10; ++i){
sum += i;
}
return sum;
}
}
最终我们能够在主线程中获取到我们想要的子线程的执行结果啦!
前10个数字的和为:55
Process finished with exit code 0
小结
Java中开启新的线程有三种方式:
第一种:继承Thread类,通过重写Thread类中的run()方法实现子线程需要完成的任务.
我们将线程对象和线程任务区分来看,当我们继承并重写run()方法的时候,我们的线程需要完成的任务和线程对象是紧密耦合在一起的.
继承Thread类的子类一定要同时重写run()方法,这两点同时进行才能满足我们的要求.
第二种:通过实现Runnable接口开启线程
通过创建Runnable接口的实现类并通过Thread的含参构造方法创建出能够执行我们制定任务的线程对象,实现了线程任务和线程对象的解耦.
不足在于我们无法获取子线程的执行结果和异常数据.
第三种:通过实现Callable接口开启线程
通过创建Callable接口的实现类并通过FutureTask(继承了Runnable接口)的含参构造方法创建出能够传递提供给Thread类使用的线程任务.最终我们能够在主线程中通过futureTask的get()方法获取子线程的执行结果和抛出的异常.
这三种实现方式是逐层递进的关系,可以好好体会一下.
第三部分Callable接口部分因为时间原因好多细节没能展开,以后有时间会补充这部分的细节.主要侧重理解这三种不同的实现方式之间的区别以及为什么会产生这些区别的原因.