线程的体系结构
根节点:
Runnable接口(线程的任务接口,其只有一个抽象方法:void run(),而run方法就是线程需要执行的任务代码)
实现类:
Thread类(只有Thread类和Thread类的子类的对象才是线程对象,且Thread类中存在着启动线程的方法start())
线程的创建方法
线程的第一种开启方式
第一种:使用继承
1. 创建一个类MyThread类,继承Thread类
2. 重写父类中的run方法:
编写线程对象的任务
4. 创建MyThraed类的对象,并调用start()方法启动线程
代码实现
package com.tan.threadstart;
public class MyThread extends Thread {
// 主动重写父类的run方法,自定义线程任务
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(" MyThread执行的次数: " + i);
}
}
}
package com.tan.threadstart;
public class ThreadTest {
public static void main(String[] args) {
// 创建Thread的子类对象
MyThread mt = new MyThread();
// 启动线程
mt.start();
for (int i = 0; i < 100; i++) {
System.out.println(" 主线程执行的次数: " + i);
}
}
}
线程的第二种开启方式
第二种:实现
1. 创建一个Runnable接口的实现类MyRunnable类的对象(用类实现或者用匿名内部类) ,并重写run方法
2. 创建Thread类对象并把Runnable的实现类MyRunnable类对象传递进去(用到的Thread类中的构造方法:Thread(Runnable target) )
3. 启动线程
代码实现
package com.tan.threadstart;
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(" MyRunnable执行的次数: " + i);
}
}
}
package com.tan.threadstart;
public class ThreadTest {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println(" 主线程执行的次数: " + i);
}
}
}
注意,我们可以发现该方法中Runnable的实现类对象只是传参,使用次数较少,可以使用匿名内部类的方式去创建Runnable的实现类对象,代码如下:
package com.tan.threadstart;
public class ThreadTest {
public static void main(String[] args) {
// 用匿名内部类创建Runnable的实现类对象,并创建Thread对象
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 线程的任务
for (int i = 0; i < 100; i++) {
System.out.println(" Runnable执行的次数: " + i);
}
}
});
// 启动线程
thread.start();
// 主线程
for (int i = 0; i < 100; i++) {
System.out.println(" 主线程执行的次数: " + i);
}
}
}
线程的第三种开启方式
第三种:有结果的线程任务
Callable<V> 接口 -> 线程的任务接口
其中V表示的是线程完成任务后,返回的结果的数据类型
Callable接口有且仅有一个方法: V call()
Callable接口和Runable很像,但是Callable接口在任务完成之后可以返回一个结果
Runnable接口和FutureTask类和Callable接口和Thread类的关系梳理
(Callable接口和其他三种类和接口没有继承或者实现的关系,是独立的)
1. Runnable接口 是 FutureTask类 的父接口,且也是Thread类的父接口( FutureTask类和Thread类也没关系,只是都实现了Runnable接口)
2. FutureTask(Callable<V> callable)
3. Thread(Runnable target)
中间类FutureTask,其有两个构造方法:
FutureTask(Callable<V> callable)
FutureTask(Runnable runnable, V result)
开启方式:
1. 创建Callable的实现类对象,并重写call方法,call和run方法一样,都是线程的任务,但是call在线程任务完成以后可以返回结果
2. 创建中间桥梁类FutureTask,FutureTask是Runnable接口的实现类,并把任务对象MyCallable类对象传递给FutureTask对象
3. 创建线程Thread对象,Thread的构造方法的形参可以放Runnable对象
4. 启动线程
注意:
FutureTask类中的V get()方法 : 可以获取线程结束的结果,但是也有阻塞效果,get()需要线程的任务全部执行完毕,才会返回结果,而main方法内的代码会顺序执行,因此会阻塞main方法内后续代码的执行
如果get方法后面还有代码,则会对后续代码产生阻塞效果
代码实现
package com.tan.threadstart;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建Callable的实现类对象
Callable<String> call = new Callable<String>() {
@Override
//call和run方法一样,都是线程的任务,但是call在线程任务完成以后可以返回结果
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("Callable执行的次数: " + i);
}
return "It's done.";
}
};
// 创建中间桥梁类FutureTask,FutureTask是Runnable接口的实现类
FutureTask<String> ft = new FutureTask<>(call);
// 创建线程Thread对象,Thread的构造方法的形参可以放Runnable对象
Thread thread = new Thread(ft);
// 启动线程
thread.start();
/*
FutureTask类中的get方法可以获取线程结束的结果,但是也有阻塞效果
如果把System.out.println("ft.get() = " + ft.get());放在下面的位置,则其处于main线程内
而get()又需要子线程的任务全部执行完毕,才会返回结果
而main方法内的代码会顺序执行,因此会阻塞main方法内后续代码的执行
因此一定会出现先执行100次Callable的线程任务,输出线程结束的结果:ft.get() = It's done.,最后才会执主线程的任务
*/
// System.out.println("ft.get() = " + ft.get());
// 在main方法里,在主线程栈里面执行
for (int i = 0; i < 100; i++) {
System.out.println("主线程执行的次数: " + i);
}
System.out.println("ft.get() = " + ft.get());
}
}