[Toc]
线程创建
作为一名优秀的程序员,怎么能不懂多线程呢?那想搞懂多线程,第一步就是先创建线程跑起来。说到创建线程,方式多种多样。其实最根本的就是三种方式,剩下的无非就是语法变一变的玩意。
创建线程的三种方式
- 继承Thread
- 实现Runnable接口
- 实现Callable接口
第一种,继承Thread.
先说为什么要继承Thread类呢?为什么不直接new 一个Thread对象来创建线程呢?原则上是可以直接new 一个Thread对象来创建一个线程。但是我们要想一下创建一个线程做什么呢?对的,肯定是想来跑我们自己的代码。我们应该知道当线程被开启执行的是run()方法。即使不知道,那就记住。这个run()方法就存在于Thread中。但是呢Thread中的run()方法几乎是没什么东西的,那我们肯定要把自己的代码写到run()方法中,那怎么来写呢?继承父类就可以重写。这样讲,就明白为什么要继承Thread类了吧。
话不多说上码
package cn.zl;
// 第一种方式创建线程,通过继承Thread。
public class ThreadTest extends Thread{
private int count = 10000;
@Override
public void run() {
while (true){
System.out.println("count"+count);
if (--count<0){
break;
}
}
}
}
既然我们的线程已经创建好,那就跑一把。
package cn.zl;
public class Main {
public static void main(String[] args) {
// 创建线程
ThreadTest threadTest = new ThreadTest();
// 启动线程
threadTest.start();
// 为了我们执行的结果可以交叉显示,所以写了以下代码。
int i=10000;
while (i>0){
System.out.println("Main"+i);
i--;
}
}
}
这时候,有个重点,就是我们线程启动,是通过start()方法,而不是直接调用run()方法。想要搞定这个东西,都需要研究一下JVM。因为start()方法其实是一个本地方法,由C/C++来实现的。但是对于我们Java程序员来讲,我们要记住线程启动时通过调用start()方法。第一种方式已经完成,学习是一个循序渐进的过程,尤其对java程序员来说,先学会怎么用,再去研究原理。
第二种,实现Runnable接口.
还是刚才那个问题,有没有不通过继承Thread的方式来重写run()方法呢?答案肯定是有的。Thread这个类对外提供了传参Runnable的实现类来重写run()方式。于是这就是我们要说的第二种了。通过实现Runnable接口中的run()方法,并把此实现类当做参数传入Thread中。
话不多说,上码
package cn.zl;
// 第二种线程创建的方式,实现Runnable
public class RunnableTest implements Runnable {
private int i = 10000;
public void run() {
while (i > 0) {
System.out.println("Runnable" + i);
i--;
}
}
}
跑一把
package cn.zl;
public class Main {
public static void main(String[] args) {
// 创建一个线程
Thread thread = new Thread(new RunnableTest());
// 启动线程
thread.start();
// 为了我们执行的结果可以交叉显示,所以写了以下代码。
int i=10000;
while (i>0){
System.out.println("Main"+i);
i--;
}
}
}
这样第二种方式就完成了。稍后讲一下三种的区别。既然存在三种,三种一定有所不同。
第三种,实现Callable接口
这一种,猛一下类似于第二种。这一种,我们不是通过实现run()方法来重写Thread中的run()方法,而是我们通过实现call()方法来重写Thread中的run()方法。
废话不多说,上码
package cn.zl;
import java.util.concurrent.Callable;
// 第三种创建线程的方式,实现Callable方式
public class CallableTest implements Callable<Integer> {
private int i = 10000;
public Integer call() throws Exception {
while (i>0){
System.out.println("Callable"+i);
i--;
}
return i;
}
}
跑一下
package cn.zl;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) {
// 创建Future对象来装载Callable的实现类对象(CallableTest)。
FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(new CallableTest());
// 创建线程
Thread thread1 = new Thread(integerFutureTask);
// 启动线程
thread1.start();
try {
// 获取线程中的返回值,并输出
System.out.println(integerFutureTask.get()+"--------------");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
// 为了我们执行的结果可以交叉显示,所以写了以下代码。
int i=10000;
while (i>0){
System.out.println("Main"+i);
i--;
}
}
}
通过以上的代码,我们可以感受到。用实现Callable方式,我们不仅能创建线程,还能获取线程的返回值。而不是像之前一样,线程启动了,到结束都不给我们打个招呼。这种方式,就比较友好。不受控,对程序员来说是一件很可怕的事情。
三种方式的优缺点
- 采用继承Thread类方式:
(1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。当然是在我们自己写的子类中使用this。
(2)缺点:因为线程类已经继承了Thread类,所有不能再继承其他的父类,这是Java语言的一大特性,单继承多实现。 - 采用实现Runable接口方式:
(1)优点:线程只是实现了Runnable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一目标对象,所有非常适合多个相同的线程处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型。
(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。 - Runnable 和Callable的区别:
(1)Callable 规定的方法是call(),Runable规定的是run();
(2)Callable 的任务执行后可返回值,而Runable的任务是不能获取返回值的。
(3)call 方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常。
(4) 运行Callable 任务可以拿到一个Future对象,表示异步计算的接口。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
start()和run() 的区别
- start()方法用来,开启线程,但是线程开启后并没有立即执行,他需要获取CPU的执行权才可以执行。
- run()方法有JVM 创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)。
消化一下,后续写更深点的东西,学习讲究循序渐进。
网友评论