这是我参与更文挑战的第5天,活动详情查看: 更文挑战
之前的文章结合软硬件讲解了计算机中的线程和进程,本篇给大家介绍java中实现多线程的几种方式。
一. 无返回值形式
JDK提供了Thread类和Runnable接口来让我们实现自己的“线程”类。
1. 继承Thread类
public class ThreadDemo {
public static class TestThread extends Thread {
@Override
public void run() {
System.out.println("TestThread");
}
}
public static void main(String[] args) {
Thread testThread = new TestThread();
testThread.start();
}
}
在调用start方法后,线程启动!
2. 实现Runnable接口
runnable实现如下,因为runnable是函数式接口,所以可以通过函数式编程来实现启动。
public class Demo {
public static class ThreadDemo implements Runnable {
@Override
public void run() {
System.out.println("ThreadDemo");
}
}
public static void main(String[] args) {
// 传统方式启动。
new Thread(new ThreadDemo()).start();
// 函数式编程启动(java1.8+)
new Thread(() -> {
System.out.println("Thread 匿名内部类");
}).start();
}
}
3. Thread类的几个常用方法
这里介绍一下Thread类的几个常用的方法:
- currentThread():静态方法,返回对当前正在执行的线程对象的引用;
- start():开始执行线程的方法,java虚拟机会调用线程内的run()方法;
- yield():yield在英语里有放弃的意思,同样,这里的yield()指的是当前线程愿意让出对当前处理器的占用。这里需要注意的是,就算当前线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程的;
- sleep():静态方法,使当前线程睡眠一段时间;
- join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait方法实现的;
4. Thread类与Runnable接口的比较:
实现一个自定义的线程类,可以有继承Thread类或者实现Runnable接口这两种方式,它们之间有什么优劣呢?
由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。 Runnable接口出现更符合面向对象,将线程单独进行对象的封装。 Runnable接口出现,降低了线程对象和线程任务的耦合性。 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。 所以,我们通常优先使用“实现Runnable接口”这种方式来自定义线程类。
二. 有返回值形式
在使用Runnable和Thread来创建线程时,run方法是不能提供返回值的。如果我们需要返回值的话,可以使用JDK提供的Callable和Future来实现。
1.Callable接口
Callable与Runnable类似,同样是只有一个抽象方法的函数式接口。不同的是,Callable提供的方法是有返回值的,而且支持泛型。Callable一般是配合线程池工具ExecutorService来使用的
// 自定义Callable
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
// 模拟计算需要一秒
Thread.sleep(1000);
return 2;
}
public static void main(String args[]) throws Exception {
// 使用
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
Future<Integer> result = executor.submit(task);
// 注意调用get方法会阻塞当前线程,直到得到结果。
// 所以实际编码中建议使用可以设置超时时间的重载get方法。
System.out.println(result.get());
}
}
2. Future接口
FutureTask是实现的RunnableFuture接口的,而RunnableFuture接口同时继承了Runnable接口和Future接口。JDK提供了FutureTask类来供我们使用。
// 自定义Callable,与上面一样
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
// 模拟计算需要一秒
Thread.sleep(1000);
return 2;
}
public static void main(String args[]) throws Exception {
// 使用
ExecutorService executor = Executors.newCachedThreadPool();
FutureTask<Integer> futureTask = new FutureTask<>(new Task());
executor.submit(futureTask);
System.out.println(futureTask.get());
}
}
在调用get方法时,可以进行取值。 FutureTask能够在高并发环境下确保任务只执行一次。