首先我们来简单回顾下线程的几种创建方式:
Runnable接口创建线程类
通过实现Runnable接口创建线程类:
- 定义Runnable接口的实现类,并重写该接口的run()方法;
- 创建Runnable实现类的实例,并以此实例作为Thread的目标对象,该Thread对象才是真正的线程对象。
继承Thread类创建线程类
通过继承Thread类创建线程类:
- 定义一个继承Thread类的子类,并重写该类的run()方法;
- 创建Thread子类的实例,即创建了线程对象;
- 通过调用该线程对象的start()方法来启动线程。
Future创建线程类
通过Callable和Future创建线程:
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
- 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程。
- 如果想获取返回值,则可以调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
多线程的坑
看下代码:
新建ThreadRun类,并简单写逻辑如下:
循环时候奇数偶数分别启动一个线程去处理业务逻辑。
![a37b965381d7381ba497253e897598a2.png](https://i-blog.csdnimg.cn/blog_migrate/0618da87ee654554335bbaa11d3d49c2.jpeg)
threadRun
main方法如下:
![506009363d2ffb7ad7e684a3885529f5.png](https://i-blog.csdnimg.cn/blog_migrate/d9d9ad1988f4cf3cafb8030e90342f9f.jpeg)
main
运行一次可能看不出什么异常,多运行几次,或则将入参num调大一些,问题就出现了。
![7f7cbbc6655e31c06af958527c75c052.png](https://i-blog.csdnimg.cn/blog_migrate/3afb05942b260d0fac5dd3c2907665b8.jpeg)
看到这个异常,不知道同学们是否熟悉,这是因为我们多个线程操作了同一个方法里面的定义的一个list,循环遍历时候,由于上一个线程还没有使用完,你就直接去循环操作,肯定是报错的了。
这时候你可能会想到加个同步锁,synchronized,锁住那个list,代码如下:
![51f64218c04b3cb0bebaf12620d59fa2.png](https://i-blog.csdnimg.cn/blog_migrate/13499e551c4ac018790cdd9ca59d1e18.jpeg)
添加synchronized关键字
加了以上关键字以后,看似问题得到了解决,但是这样的效率还不如一个单线程。
因为 你是硬生生的将多线程变成了单线程操作。
这里还有个问题,由于我们的main方法是要获取这个list的返回值的,我们在循环不睡眠的情况下,看着结果还是正常得到了返回。长度也是我们所期望的,但是我们不妨加一个睡眠看看,注意,睡眠加到threadRun方法,循环到基数的时候,睡眠一秒,代码如下:
![4fc134f31049e445c0594dd3906c241e.png](https://i-blog.csdnimg.cn/blog_migrate/2a13ff5359be0e185219df97c2abf157.jpeg)
这时候我们看到list的长度并不是我们所期望的10,并且程序好像也没有执行完就返回了。
那么我们可以让main方法睡眠久一点:
![68eae7ae1e9e36e32eedd1e1b8468eb5.png](https://i-blog.csdnimg.cn/blog_migrate/2be30d24413891236b64f8061d47da0e.jpeg)
再看下输出的结果就是我们所希望的了,但是这样并不好,这是我们知道了线程执行大致所需要的时间了,所以睡眠时间超过它还是可以返回全部结果的,那么实际中如果我们不知道程序需要运行多久呢?你还怎么处理?
这时候Future就出现了:
![745e6820c02a3c957a3872804d37b672.png](https://i-blog.csdnimg.cn/blog_migrate/1daa28f8fe4eb967d9bbf06941c0894d.jpeg)
ExcuteRun类
![9672858f3de97d86241d0139d1ccd262.png](https://i-blog.csdnimg.cn/blog_migrate/918f17723fde33e5bb114f24905f1f1f.jpeg)
ExcuteRun类的内部类future对象
我们再使用这个的时候不加任何睡眠结果是正常返回的,那么有同学可能该问了,如果加睡眠是不是就返回错了呢?我们再ExcuteRun方法里面添加睡眠看下结果:
![ef488476997503fceff148be3bcb36b7.png](https://i-blog.csdnimg.cn/blog_migrate/ed686304c8194149aecb69866aa4e2c9.jpeg)
我们还是在循环到奇数时候添加睡眠,可以看到日志隔一秒一输出,最后结果还是全部输出。
看到这里,同学们可以多多回想下代码中的坑。结合实际业务场景,多多练习下。
结论
如果是不想要返回结果可以用Thread类,或则Runable接口。如果是想要返回结果还是老老实实用Future吧。
有关多线程的问题,评论区我们一起来讨论学习吧。