java使用多种方式实现多线程及线程池的使用
一、多线程简介
线程是cpu独立调度得单位,通过引入线程,实现时分复用,利用并发思想使得程序运行的更加迅速,多线程得目的,就是最大限度得利用cpu得资源 线程与语言无关,由操作系统实现,主要有三种实现方式,用户级线程,内核级线程,用户级线程和内核级线程
二、多线程实现了什么?
为了解决负载均衡问题,充分利用CPU资源.为了提高CPU的使用率,采用多线程的方式去同时完成几件事情而不互相干扰.为了处理大量的IO操作时或处理的情况需要花费大量的时间等等,比如:读写文件,视频图像的采集,处理,显示,保存等
三、多线程的使用
在java中,多线程得主要实现方式有四种:继承Thread类,实现Runnable接口、实现callable接口通过FutureTask包装器来创建Thread线程,使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,而后两种是带返回值的。除此之外,通过Timer启动定时任务,或者通过像Spring Task和quartz这样的第三方任务调度框架也可以开启多线程任务
1、继承Thread类创建线程
Thread类本质上也是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程比较简单,通过继承Thread类并复写run()方法,就可以启动新线程并执行自己定义的run()方法。
输出结果:
2、实现Runnable接口创建线程 由于Java是单继承机制,如果自己的类已经继承自另一个类,则无法再直接继承Thread类,此时,可以通过实现Runnable接口来实现多线程。
实现Runnable接口并实现其中的run方法,然后通过构造Thread实例,传入Runnable实现类,然后调用Thread的start方法即可开启一个新线程。
输出结果:
3、实现Callable接口通过FutureTask包装器来创建Thread线程
首先需要一个实现Callable接口的实例,然后实现该接口的唯一方法call逻辑,接着把Callable实例包装成FutureTask传递给Thread实例启动新线程。FutureTask本质上也实现了Runnable接口,所以同样可以用来构造Thread实例。
运行结果:
4、使用ExecutorService、Callable、Future实现有返回结果的线程(线程池方式)
ExecutorService、Callable、Future三个接口都是属于Executor框架。可返回值的任务必须实现Callable接口。通过ExecutorService执行Callable任务后,可以获取到一个Future的对象,在该对象上调用get()就可以获取到Callable任务返回的结果了。
注意:Future的get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
输出结果:
5、其他创建线程的方式
当然,除了以上四种主要的线程创建方式之外,也还有很多其他的方式可以启动多线程任务。比如通过Timer启动定时任务,或者通过像Spring Task和quartz这样的第三方任务调度框架也可以开启多线程任务,关于第三方任务调度框架的例子还请查询相关资料。
四、java中线程池得使用
1、为什么不适用 new Thread?
首先从我秉持的原则入手,“简洁优雅”。试想如果在一段代码中你需要创建很多线程,那么你就不停地调用 new Thread(...).start() 么?显然这样的代码一点也不简洁,也不优雅。初次之外这样的代码还有很多坏处:
每次都要新建一个对象,性能差; 建出来的很多个对象是独立的,缺乏统一的管理。如果在代码中无限新建线程会导致这些线程相互竞争,占用过多的系统资源从而导致死机或者 oom; 缺乏许多功能如定时执行、中断等。
从这些坏处很容易可以看出解决方法,那就是弄一个监管者来统一的管理这些线程,并将它们存到一个集合(或者类似的数据结构)中,而且还要动态地分配它们的任务。当然Java已经给我们提供好十分健全的东西来使用了,那就是线程池!
Java线程池 Java提供了一个工厂类来构造我们需要的线程池,这个工厂类就是 Executors 。这个类提供了很多方法,我们这里主要讲它提供的4个创建线程池的方法,即
newCachedThreadPool() newFixedThreadPool(int nThreads) newScheduledThreadPool(int corePoolSize) newSingleThreadExecutor()
newCachedThreadPool: 这个方法正如它的名字一样,创建一个可缓存的无界线程池,如果线程池长度超过处理需要,可灵活回收空线程,若无可回收,则新建线程。当线程池中的线程空闲时间超过60s,则会自动回收该线程,当任务超过线程池的线程数则创建新的线程,线程池的大小上限为Integer.MAX_VALUE,可看作无限大。
输出结果:
newFixedThreadPool(int nThreads)#
创建一个指定大小的线程池,可以看到这个方法中带了一个参数,这个方法创建的线程池是定长的,这个参数就是线程池的大小。也就是说,在同一时间执行的线程数量只能是 nThreads 这么多,这个线程池可以有效的控制最大并发数从而防止占用过多资源。超出的线程会放在线程池的一个队列里等待其他线程执行完,它是一个无界队列
输出结果:
可以看到我创建了一个大小为3的线程池,也就是说它支持的最大并发线程数是3,运行后发现这些数确实是3个3个为一组输出的。
合理得设置定长线程池是一件非常重要的事
从 Scheduled 大概可以猜出这个线程池是为了解决上面说过的第3个坏处,也就是缺乏定时执行功能。这个线程池也是定长的,参数 corePoolSize 就是线程池的大小,即在空闲状态下要保留在池中的线程数量。
而要实现调度需要使用这个线程池的 schedule() 方法 (注意这里要把新建线程池的返回类 ExecutorService 改成 ScheduledExecutorService 噢)
输出结果
newSingleThreadExecutor() 这个线程池就比较简单了,他是一个单线程池,只使用一个线程来执行任务。但是它与 newFixedThreadPool(1, threadFactory) 不同,它会保证创建的这个线程池不会被重新配置为使用其他的线程,也就是说这个线程池里的线程始终如一。
输出结果
pool-1-thread-1:0
pool-1-thread-1:1
pool-1-thread-1:2
pool-1-thread-1:3
pool-1-thread-1:4
pool-1-thread-1:5
pool-1-thread-1:6
pool-1-thread-1:7
pool-1-thread-1:8
pool-1-thread-1:9
六、小结
多线程在平时还是经常使用的,当然,我这些只是线程得一些基本使用,想要用好线程,还需要多加学习
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix