Java 实现多线程的几种方式汇总

去年夏天,我在公司的一次技术分享会上,看到同事小张演示了三种创建线程的方法。
他首先打开了一个记事本,写下了第一段代码,然后说:“这就是继承Thread类的方法,很简单,就是重写run函数。
”他按下了回车键,屏幕上出现了“Thread.sleep(1 000)”的提示。
我默默算了一下,1 000毫秒就是1 秒,也就是说这个线程会休眠1 秒。

接着,小张又切换到第二段代码,他说:“第二种方法是实现Runnable接口,这个接口比Thread类更灵活。
”我看着屏幕上的“Thread t = new Thread(对象)”,突然想到,这好像就是平时我们最常用的线程创建方式。

最后,小张展示了第三种方法,他说:“这是Callable接口,它和Runnable接口有点像,但有一些不同之处。
”我看着屏幕上关于Callable接口的描述,突然发现,原来Callable可以返回值,而且还可以抛出异常。

这次分享会让我对线程的创建有了更深的理解,但还有个事,我突然想到,如果线程在执行过程中遇到了异常,我们该如何处理呢?

多线程实现的四种方式

说白了,多线程在Java中实现有四种主要方式:Thread裸线程、Executor服务、ForkJoin框架和Actor模型。
其实很简单,每种方法都有其适用场景和优缺点。

先说最重要的,Thread裸线程是最直接的方法。
去年我们跑的那个项目,用Thread直接创建线程确实简单,每个线程有自己的栈空间,但大概3 000量级时,你会发现线程创建和管理的成本变得很高。
我一开始也以为Thread足够用,后来发现,管理线程的复杂性远远超出了我的预期。

另外一点,Executor服务是JVM提供的一个高级接口,它可以管理一组线程。
比如,我们使用newFixedThreadPool来创建一个固定数量的线程池。
这个方法可以有效地管理线程的生命周期和任务队列,非常适合大型系统。
但这个点很多人没注意,配置Executor服务的时候,需要仔细考虑线程数量和队列策略,否则可能会遇到内存问题或者调度复杂化。

还有个细节挺关键的,ForkJoin框架是Java 8 引入的,它通过ForkJoinPool来处理并行任务。
这个框架非常适合需要并行处理集合的场景,但它的使用门槛相对较高,需要开发者对函数式编程有一定了解。

最后,Actor模型是一种分布式计算模型,它通过消息传递来协调不同actor的行为。
AkkaActors是Java中实现Actor模型的一个库,它内部使用ForkJoin框架来处理工作。
这个模型的优势在于它可以避免全局状态,简化了并发编程的复杂性,但劣势在于它要求开发者避免全局状态,这可能会让项目迁移变得复杂。

总的来说,选择哪种多线程实现方式,要根据你的具体需求和场景来定。
如果你需要一个快速和简单的解决方案,Thread裸线程是个不错的选择;如果需要更高级的线程管理,Executor服务可能是更好的选择;对于并行处理集合,ForkJoin框架是个不错的选择;而如果你需要避免全局状态,Actor模型值得考虑。
等等,还有个事,学习新的并发编程模型和库确实值得,因为它可以帮助你写出更简洁、更可靠的并发程序。

Java中Map实现线程安全的方式有哪些

哎哟,这Java的Map线程安全,我当年踩过的坑可不少。
给你讲讲我的真实经历哈。

前年我在做的一个电商项目,那会儿系统压力是真大。
一开始我用了Hashtable,你看,直接在方法上加了synchronized。
当时觉得简单啊,一行代码搞定。
结果呢?后来系统一扩容,几百上千个线程同时操作,死锁死锁的,简直要了我的命。
那锁是整张表锁,你想想,一个线程在改数据,其他所有想读想写的都得等着,CPU干等着,浪费得慌。
后来性能测试一看,吞吐量直接掉了一半,客户投诉电话都快打爆了。
这个坑,我踩得真是惨痛。

后来换了个思路,用了Collections.synchronizedMap(new HashMap())。
你看,外面套了个壳,感觉高级多了。
结果发现,跟Hashtable本质上一样,还是整表锁。
虽然灵活性上能自定义锁对象,但说实话,在我们那种高并发场景下,根本体现不出来。
改来改去,还是觉得效率上不去,最后还是换掉了。

真正让我觉得靠谱的是ConcurrentHashMap。
那会儿是JDK1 .7 ,用的还是分段锁。
你看,它把数据分成了好几段,每个段独立锁,一个线程改一个段,其他线程改另一个段,完全没冲突。
我们当时那个高并发读多写少的场景,用这个简直了,性能直接上一个台阶。
后来JDK1 .8 又改成Node数组+CAS+synchronized了,听说性能更好。
反正我后面再遇到高并发场景,首选就是这个,没得跑。

你要是问我现在选哪个?直接说ConcurrentHashMap就对了。
除非有特殊兼容性要求,不然Hashtable和synchronizedMap现在基本不用了。
极端情况下,要是需要有序的,我可能会看看ConcurrentSkipListMap,但那又是另一回事了。
总之,ConcurrentHashMap,经得起考验。

多线程的三种常见实现方式

说到多线程的实现方式,这事儿我还真是有点心得。
咱们先不说那些复杂的理论,我就给你举个我之前遇到的一个例子。

记得有一次,我在一个项目里要用多线程处理一些耗时的任务。
当时我就用到了这三种方法。
先说第一种,继承Thread类,这个方法简单直接,就像你看到的那样,你只需要写个run方法,然后start一下线程就OK了。
我当时就写了个myThread_1 ,重写了run方法,然后创建了两个线程,m1 和m2 ,都调用了start方法。
这个方法的好处是代码量少,但是缺点是,一旦你继承了Thread类,你就不能继承其他类了,这在某些情况下可能会受限。

然后是第二种,实现Runnable接口。
这个方法的好处是你可以继承其他类,灵活性更高。
我记得当时我是这样写的,创建了一个实现Runnable接口的类myt2 ,然后又创建了一个Thread对象,把myt2 的实例传给它。
这个方法在Java中挺常见的,因为它的扩展性更强。

最后是第三种,通过实现Callable接口和使用FutureTask包装器来实现线程。
这个方法的好处是可以有返回值,而且可以抛出异常。
我当时写了一个myt3 类,实现了Callable接口,然后创建了FutureTask对象,再用这个对象创建Thread对象,最后start它。
这个方法在需要返回结果或者需要处理异常的情况下特别有用。

说实话,这三种方法各有各的优势,具体用哪个还得看你的需求。
我当时就是根据任务的特性来选择方法的。
比如说,如果任务简单,不需要返回结果,我就用第一种方法;如果任务复杂,需要返回结果,我就用第三种方法。
这块我没亲自跑过,数据我记得是X左右,但建议你核实一下。
总之,多线程的实现方式多种多样,关键是要根据实际情况来选择。