JAVA基础知识之多线程——三种实现多线程的方法及区别
所有JAVA线程都必须是Thread或其子类的实例。
继承Thread类创建线程
步骤如下,
- 定义Thead子类并实现run()方法,run()是线程执行体
- 创建此子类实例对象,即创建了线程对象
- 调用线程对象的start()方法来启动线程
下面是一个例子,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package threads; public class FirstThread extends Thread { //通过继承thread方式实现多线程 //i不会被多个线程共享 private int i; public void run() { for (; i< 20 ; i++) { //System.out.println(Thread.currentThread().getName()+" "+i); //System.out.println(this.getName()+" "+i); //if extends Thread, here this==Thread.currentThread() System.out.println(getName()+ " " +i); } } public static void main(String[] args) { for ( int i= 0 ; i< 50 ; i++) { System.out.println(Thread.currentThread().getName()+ " " +i); if (i== 20 ){ new FirstThread().start(); new FirstThread().start(); } } } } |
执行结果, 可见thread-5和thread-6的i值都是从0开始,互不影响
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | main 0 main 1 main 2 main 3 main 4 main 5 main 6 main 7 main 8 main 9 main 10 main 11 main 12 main 13 main 14 main 15 main 16 main 17 main 18 main 19 main 20 Thread- 5 0 Thread- 5 1 Thread- 5 2 Thread- 5 3 main 21 Thread- 5 4 main 22 Thread- 5 5 main 23 Thread- 5 6 main 24 Thread- 5 7 main 25 Thread- 6 0 main 26 Thread- 6 1 main 27 Thread- 6 2 main 28 Thread- 6 3 main 29 Thread- 6 4 main 30 Thread- 6 5 main 31 Thread- 6 6 main 32 Thread- 6 7 main 33 Thread- 6 8 main 34 main 35 Thread- 6 9 main 36 Thread- 6 10 main 37 Thread- 6 11 main 38 Thread- 6 12 main 39 Thread- 6 13 main 40 Thread- 6 14 main 41 Thread- 6 15 main 42 Thread- 6 16 main 43 Thread- 6 17 main 44 Thread- 6 18 main 45 Thread- 6 19 main 46 main 47 main 48 main 49 Thread- 5 8 Thread- 5 9 Thread- 5 10 Thread- 5 11 Thread- 5 12 Thread- 5 13 Thread- 5 14 Thread- 5 15 Thread- 5 16 Thread- 5 17 Thread- 5 18 Thread- 5 19 |
实现Runable接口创建线程类
步骤如下,
- 定义Runable的实现类,重写run()方法作为线程执行体
- 创建Runable实现类的实例对象,并将此实例对象作为Thread的targe再创建线程对象,此线程对象才是真正的子线程对象。
- 调用线程对象的start()方法启动线程
具体例子如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package threads; public class SecondThread implements Runnable { //i将被多个线程共享 private int i; @Override public void run() { // must use Thread.currentThread when implements Runnable for (; i< 20 ; i++) { System.out.println(Thread.currentThread().getName()+ " " +i); } } public static void main(String[] args) { for ( int i= 0 ; i< 50 ; i++) { System.out.println(Thread.currentThread().getName()+ " " +i); if (i== 20 ){ SecondThread st = new SecondThread(); //多个线程共享了同一个target, 将会共享i new Thread(st, "new thread 1" ).start(); new Thread(st, "new thread 2" ).start(); } } } } |
执行结果, 可以看到子线程 new Thread1和new thread2 的i是连续的,共享了同一个i值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | main 0 main 1 main 2 main 3 main 4 main 5 main 6 main 7 main 8 main 9 main 10 main 11 main 12 main 13 main 14 main 15 main 16 main 17 main 18 main 19 main 20 new thread 1 0 new thread 1 1 new thread 1 2 new thread 1 3 main 21 new thread 2 4 main 22 new thread 2 5 new thread 1 4 main 23 new thread 1 7 main 24 new thread 1 8 main 25 new thread 2 6 new thread 1 9 new thread 2 10 new thread 1 11 new thread 2 12 main 26 new thread 2 14 main 27 new thread 2 15 main 28 new thread 1 13 main 29 new thread 1 17 main 30 new thread 1 18 main 31 new thread 1 19 main 32 main 33 main 34 new thread 2 16 main 35 main 36 main 37 main 38 main 39 main 40 main 41 main 42 main 43 main 44 main 45 main 46 main 47 main 48 main 49 |
使用Callable和Future创建线程
Callable接口
Callable接口特点,与Runable的区别
- Callable类似于Runable的增强版,区别在于Callable是可以有返回值,并且可以抛出异常的。
- Callable中有一个call()方法,可以作为线程的执行体,但是线程执行体不会被直接调用,因为无法直接获取子线程返回值,
- Callable接口不是Runable接口,所以无法作为Thread的target来像Runable那样创建线程
基于以上三点,Future接口派上用场了,
Future接口
Future接口提供了一个FutherTask实现类,此实现类还实现了Runable接口,因此它的实例可以作为Thread类的target,与Callable结合使用从而实现多线程。
Future接口中有如下方法控制线程,
cancel(..)取消关联的Callable任务
get(..)获取关联的Callable钟call()方法的返回值,这里解决了Callable实现多线程但无法直接调用call()获取子线程返回值的问题
get(timeout, unit).
isCancelled()
isDone()
- 使用Callable和Future创建线程的步骤如下,
- 创建Callable的实现类,并实现call()方法作为线程执行体
- 使用FutureTask类来包装Callable对象
- 使用FutureTask类对象作为Thread的target来创建子线程
- 调用FutureTask类对象的get()方法获取子线程结束后的返回值,此过程可以抛出异常
下面是一个例子,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | package threads; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class ThirdThread { public static void main(String[] args) { ThirdThread rt = new ThirdThread(); // FutureTask 是一个包装类,封装Callable对象 FutureTask<Integer> task = new FutureTask<Integer>( new Callable<Integer> () { //call()将作为子线程的执行体 @Override public Integer call() throws Exception { int i = 0 ; for (; i< 20 ; i++) { System.out.println(Thread.currentThread().getName()+ " " +i); } return i; } }); for ( int i= 0 ; i< 100 ; i++) { System.out.println(Thread.currentThread().getName()+ " " +i); if (i == 20 ) { new Thread(task, "new thread" ).start(); } } try { System.out.println( "Return value from sub thread: " +task.get()); } catch ( Exception e) { e.printStackTrace(); } } } |
执行结果,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | main 0 main 1 main 2 main 3 main 4 main 5 main 6 main 7 main 8 main 9 main 10 main 11 main 12 main 13 main 14 main 15 main 16 main 17 main 18 main 19 main 20 main 21 main 22 main 23 main 24 main 25 new thread 1 0 main 26 new thread 1 1 main 27 new thread 1 2 main 28 new thread 1 3 main 29 new thread 1 4 main 30 new thread 1 5 main 31 new thread 1 6 main 32 new thread 1 7 main 33 new thread 1 8 main 34 new thread 1 9 main 35 new thread 1 10 main 36 new thread 1 11 main 37 new thread 1 12 main 38 new thread 1 13 main 39 new thread 1 14 main 40 new thread 1 15 main 41 new thread 1 16 main 42 new thread 1 17 main 43 new thread 1 18 main 44 new thread 1 19 main 45 main 46 main 47 main 48 main 49 Return value from sub thread: 20 |
创建线程的三种方式对比
Callable与Runable方式基本相同,区别是Callable有返回值并且可以抛出异常。
Callable与Runable优缺点,
- 线程类只是实现了Callable和Runable接口,还可以继承别的类,扩展性强,更灵活
- 多个线程可以共享同一个定义在现成体中的变量,可以方便实现资源共享
缺点是编程比较复杂,且访问当前线程必须要 Thread.currentThread()
采用继承Thread实现多线程的优缺点,
- 优点是编程简单,且直接使用this就可以访问当前线程
- 缺点是不够灵活,已经继承了Thread类,就不能继承别的父类。
因此一般使用第三种方式实现多线程。即Callable接口结合Runable接口的方式
分类:
JAVA
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构