前言
了解完一些基本概念之后,就开始实践吧。
实现多线程
Java 的 JDK 开发包已经自带了对并发编程技术的支持,通过它可以方便地进行多线程编程。实现多线程主要有几种方式。
方式1:继承 Thread 类
创建一个自定义的线程类 MyThread1
,它继承自 Thread
类,并且重写 run()
方法,在 run()
方法中添加线程要执行的任务代码,最后通过 Thread 调用 start()
方法来启动线程。示例如下:
public class MyThread1 extends Thread{
@Override
public void run() {
super.run();
System.out.println("继承 Thread 的线程运行了");
}
}
public class Run {
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
// 后执行
myThread1.start();
// 大概率先执行
System.out.println("程序运行结束");
}
}
多线程是异步的,线程执行的顺序未必是编辑器中代码从上到下的顺序。
执行 myThread1.start();
这一句比较耗时,所以大概率(不是绝对)会先执行输出字符串“程序运行结束”。
方式2:实现 Runnable 接口
由于 Java 是单根继承,不支持多继承,如果线程类已经继承了其它类,就不能再继承自 Thread 类,所以需要实现 Runnable 接口来解决这种情况。
新建一个自定义的线程类 MyThread2
,它已经继承自 AServer
类,再实现 Runnable
接口,也重写其中的 run()
方法。通过实现 Runnable 接口,可间接地实现“多继承”的效果。示例如下:
public class MyThread2 extends AServer implements Runnable{
@Override
public void saveData() {
System.out.println("B中保存数据的方法被执行了");
}
@Override
public void run() {
System.out.println("实现 Runnable 接口的线程");
saveData();
}
}
public class Run {
public static void main(String[] args) {
// 实现 Runnable 接口
Runnable runnable = new MyThread2();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("程序运行结束");
}
}
在编辑器 IDEA 中按住 Ctrl
+ 鼠标左键点击 Thread
类,打开其源码,可以看到它实现了 Runnable
接口。
这意味着 Thread
类的构造函数 Thread(Runnable target) 不仅可以传入 Runnable
接口的对象,而且可以传入一个 Thread
类的对象,这样做可以将一个 Thread
对象中的 run()
方法交给其他线程进行调用。
方式3:实现 Callable 接口
无论是使用 Thread
还是 Runnable
都没有返回值,所以 JDK 1.5 推出了 Callable
接口。
Callable<V>
接口位于java.util.concurrent
包中,它更像是 Runnable 接口的增强版,call()
方法不仅有返回值还有抛出异常的功能。Future<V>
接口也位于同一个包中,它的一个实现类FutureTask<V>
可以用来保存call()
方法的返回值,并作为Thread
类构造函数的 target。- 使用
FutureTask<V>
的get()
方法获取返回值。
新建一个自定义的线程类 MyThread3
,实现 Callable<V>
接口,这里设定返回值类型为 String
。示例如下:
import java.util.concurrent.Callable;
public class MyThread3 implements Callable<String> {
@Override
public String call() throws Exception {
return "实现 Callable 接口,有返回值";
}
}
public class Run {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3. 实现 Callable 接口
MyThread3 mt3 = new MyThread3();
FutureTask<String> ft = new FutureTask<>(mt3);
// Thread 类接收 FutureTask 类对象
Thread thread2 = new Thread(ft);
thread2.start();
// 同步方式获取返回结果
String result = ft.get();
System.out.println(result);
}
}
方式4:使用线程池
在编辑器 IDEA 中使用 new Thread()
创建多线程时,它会提示:不要显式创建线程,请使用线程池。
新建一个自定义线程类 MyThread4
,实现 Callable<V>
接口,这里设定返回值类型为 String
。示例如下:
public class MyThread4 implements Callable<String> {
@Override
public String call() throws Exception {
// 返回 0 - 100 的随机整数
return "当前线程名字:" + Thread.currentThread().getName() + ",随机数:" + new Random().nextInt(100);
}
}
为了简单点(偷懒),线程池可以这样写:
public class Run {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 4. 使用线程池创建线程
ExecutorService es = Executors.newFixedThreadPool(10);
// 结果集
List<Future<String>> resultList = new ArrayList<>();
// 创建 20 个任务并执行
for (int i = 0; i < 20; i++) {
// 使用 ExecutorService 执行 Callable 类型的任务,并将结果保存在 task 变量中
Future<String> task = es.submit(new MyThread4());
// 将任务执行结果存储到 List 中
resultList.add(task);
}
es.shutdown();
// 遍历结果集
for (int i = 0; i < resultList.size(); i++){
Future<String> result = resultList.get(i);
// 打印各个线程(任务)执行的结果
System.out.println(result.get());
}
}
但是,阿里巴巴的 Java 开发手册中,禁止使用 Executors 创建线程池,虽然 Executors 极大的简化了我们创建各种类型线程池的方式。
所以,最好使用 ThreadPoolExecutor
创建线程:
public class Run {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 5. 使用 ThreadPoolExecutor 创建线程池
// 核心线程数
int corePoolSize = 5;
// 最大线程数
int maxnumPoolSize = 10;
// 指非核心线程的空闲时间达到一定的阈值会被回收
long keepAliveTime = 2;
// 以秒为单位
TimeUnit timeUnit = TimeUnit.SECONDS;
// 工作队列,存放提交的等待执行任务
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(5);
ThreadPoolExecutor service = null;
try {
// 创建线程池
service = new ThreadPoolExecutor(corePoolSize, maxnumPoolSize, keepAliveTime,
timeUnit, workQueue, new ThreadPoolExecutor.AbortPolicy());
// 循环提交任务
for (int i = 0; i < 20; i++){
Future<String> task = service.submit(new MyThread4());
// 打印输出结果
System.out.println(task.get());
// 线程休眠 1 S,再提交下一个任务,保证顺序
Thread.sleep(1000);
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
service.shutdown();
}
}
}
分析
在编辑器 IDEA 中,打开 ThreadPoolExecutor
的源码,可以看到其构造方法中使用了 Executors.defaultThreadFactory()
。
继续点击溯源,如下图所示,可以看到这个静态方法 defaultThreadFactory()
返回了一个 ThreadFactory
类型的 DefaultThreadFactory
实例。
继续点击溯源,如下图所示,可以看到这个私有静态类 DefaultThreadFactory
实现了 ThreadFactory
接口,其中它设置了一些默认值:线程池的序号 poolNumber
设置为 1,线程的序号 threadNumber
设置为 1。
- 在其构造函数中,设置线程所属的线程组
group
,设置线程的名字前缀namePrefix
。 - 在其
newThread(Runnable r)
函数中,根据其私有属性:group
、namePrefix
、threadNumber
创建了一个新线程,然后再设置该线程不是守护线程、该线程的优先级为普通等级。
那么,可以说:虽然线程工厂设置了很多属性,但最终还是通过 new Thread()
来创建线程的,只不过构造函数传入的参数比较多。
无论表面上封装了多少层,只要本质不变,打开封装后,就会知道最终还是继承 Thread 类或实现 Runnable 接口得到的。
参考
Java 全栈知识体系