文:两年了,才知道如何实现多线程 ,哎
注:中国传统文化,先仔细看,若有用,再点赞, 给自己一点思考的时间
注:微信搜索:CodeCow,关注这个非常 SAO 的程序员
一、为啥会有这篇文章
时光飞逝,回到2017年,年末;小编刚自学完编程以及三大框架,可谓 信心满满,剑指offer。
殊不知,在第一场面试中,被一位小杨(年龄不大)面试官上了一课;这些年过去了,可谓记忆犹新。
- 小杨:来,你说说多线程
- 阿牛:线程是进程中的一个执行单元。。。
- 小杨:那多线程有几种实现方式
- 阿牛:2种(毫不犹豫,这谁还不知道!!)
- 小杨:那这两种有啥区别
- 阿牛:嗯。。。(瞬间懵B)
就这样,我在忐忑中,接受着小杨的各种拷问,最终以“回去等通知吧”而结束;
面试完,虽隐约能猜到结果,但却也渴望上天,天平的倾斜;
阿牛苦等几天,犹隔三秋,结果以 GG 而结束。
二、浅聊,如何实现多线程
小伙伴们都知道,一个程序在 没有跳转语句 的前提下,都是 由上至下 依次执行。
那现在想要设计一个程序,“边撸代码”且“ 边看大片(正经人) ”,怎么设计?
首先,要解决上述问题,我们得 聊聊多线程,那肯定的先了解什么是 线程 和 进程
在「java编程思想」书的 并发模块 中,有这么两句话,用来描述进程和线程
进程:指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,进程其实就是程序的一次执行过程,是系统运行程序的基本单位
线程:指进程中的一个单一的顺序控制流
译:线程其实就是进程中的一个执行单元,负责当前进程中程序的执行
这两句话虽然有点抽象,但却说出了线程和进程的本质。
来来来,我们随便看看,小编系统中的“ 进程”
接下来,我们再看看,小编电脑管家中的“ 线程”
在了解完线程和进程之后,实现多线程可谓“so easy”
接下来简单讲几种实现多线程的方式,别再 “两年后了”
三、继承Thread类,了解下
又到 “装B” 的时候了,来来来,带大家看一下 Thread类的源码
接下来,看下Thread类的 “start()方法”
从源码可以看出,Thread类本质上是实现了Runnable接口的一个实例。
因此,启动线程的唯一方式就是通过Thread类的start()方法,start()方法是个 native方法,它会启动一个新的线程,并执行run()方法。
来,接下来,整点 SAO操作
首先,创建 TestThread类(正经人,别多想)
/**
* Create By CodeCow on 2020/8/3.
*/
public class TestThread {
public static void main(String[] args) {
MyThread mt = new MyThread("新线程————看大片");
//开启新线程
mt.start();
//在主方法中执行for循环
for (int i = 0; i < 5; i++) {
System.out.println("main线程————撸代码,没意思。。" + i);
}
}
//继承Thread类
public static class MyThread extends Thread {
//定义指定线程名称的构造方法
public MyThread(String name) {
//调用父类的String参数的构造方法,指定线程的名称(原理:利用继承特点,将线程名称传递)
super(name);
}
//重写run方法,定义线程要执行的代码
@Override
public void run() {
for (int j = 0; j < 5; j++) {
//getName()方法 来自父亲(就是Thread类中,获取当前线程名称方法)
System.out.println(getName() + " :好刺激哟,不行了,快、快。。" + j);
}
}
}
}
接下来,咋们 运行下 TestThread,看看是啥结果
Connected to the target VM, address: '127.0.0.1:56321', transport: 'socket'
main线程————撸代码,没意思。。0
main线程————撸代码,没意思。。1
新线程————看大片 :好刺激哟,不行了,快、快。。0
main线程————撸代码,没意思。。2
新线程————看大片 :好刺激哟,不行了,快、快。。1
main线程————撸代码,没意思。。3
新线程————看大片 :好刺激哟,不行了,快、快。。2
新线程————看大片 :好刺激哟,不行了,快、快。。3
新线程————看大片 :好刺激哟,不行了,快、快。。4
main线程————撸代码,没意思。。4
Disconnected from the target VM, address: '127.0.0.1:56321', transport: 'socket'
不难发现“ 撸代码线程”和“ 看大片线程 ”各执行了五次
因此,用这种方式启动线程,直接用 自己的类继承Thread类,并重写他的run()方法 就可以启动线程,并执行自己定义的run()方法了,就可以了。这操作,不6吗!
[图片上传失败...(image-7dfae8-1596706852557)]
四、实现Runnable接口,了解下
这次,小编就不“装B”,直接带大家看一下 Runnable类的源码
从源码不难发现,Runnable接口从“Java1.0”就已经有了,它内部只有一个抽象方法run()。
因此:要启动线程就要实现Runnable接口并重写它的run()方法。
- 注意:由于Java不支持多继承,如果自己的类已经继承了其他类,要启动线程就要实现Runnable接口并重写它的run()方法
来来来,实操整一波
首先,创建 TestRunnable类,并实现Runnable接口
/**
* Create By CodeCow on 2020/8/3.
*/
public class TestRunnable implements Runnable{
//重写run()方法, 写自己需要的代码
@Override
public void run() {
for (int i = 0; i < 5; i++) {
//currentThread() 返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName()+" :好刺激哟,不行了,快、快。。" + i);
}
}
public static void main(String[] args) {
//创建自定义类对象 线程任务对象
TestRunnable mr = new TestRunnable();
//创建线程对象
Thread t = new Thread(mr, "新线程————看大片(正经人别多想)");
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("main线程————撸代码,没意思。。" + i);
}
}
}
接下来,咋们 运行下TestRunnable,看看又会是啥结果呢
Connected to the target VM, address: '127.0.0.1:56275', transport: 'socket'
main线程————撸代码,没意思。。0
新线程————看大片(正经人别多想) :好刺激哟,不行了,快、快。。0
main线程————撸代码,没意思。。1
新线程————看大片(正经人别多想) :好刺激哟,不行了,快、快。。1
main线程————撸代码,没意思。。2
新线程————看大片(正经人别多想) :好刺激哟,不行了,快、快。。2
main线程————撸代码,没意思。。3
新线程————看大片(正经人别多想) :好刺激哟,不行了,快、快。。3
main线程————撸代码,没意思。。4
新线程————看大片(正经人别多想) :好刺激哟,不行了,快、快。。4
Disconnected from the target VM, address: '127.0.0.1:56275', transport: 'socket'
不难发现 “ 撸代码线程” 和 “看大片线程” 同样也各执行了五次。稳了,稳了
稳是稳了,但很多人肯定会认为,这么写不 Low 吗;
Low,Low到死,毕竟JDK都快出15了,连 8 的 “Lambda” 还不会用吗!!!
来来来,看看使用 Lambda表达式 + 匿名内部类 的 SAO操作
/**
* Create By CodeCow on 2020/8/3.
*/
public class TestRunnableByLambda {
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("新线程————看大片(别多想) :好刺激哟,不行了,快、快。。" + i);
}
}).start();
for (int i = 0; i < 5; i++) {
System.out.println("main线程————撸代码,没意思。。" + i);
}
}
}
其结果不言而喻,也是 “撸代码线程” 和 “看大片线程” 各自执行了五次
[图片上传失败...(image-c9d25f-1596706852557)]
然并卵,同样在实际开发中,并非像上篇文章《答应我,别再if/else校验请求参数了可以吗》那么简单。
也就是说,并非 3 + 2 - 5 * 0 这么简单
假如有需求:需要让异步执行的线程在执行完成后返回一个值给当前的线程,当前的线程需要依赖这个值做一些其他的业务操作!
此时,怎么办!别慌,Callable登场
五、Callable 了解下
同样,也带大家喽一眼 Callable类的源码
可以看出,Callable接口是 Java1.5 就开始出现了,并且 只有一个带返回值的call()方法。
那么,问题来了,怎么写带返回值的线程按?
淡定,看我表演
首先,创建 TestCallable类,并实现Callable接口
/**
* Create By CodeCow on 2020/8/3.
*/
@Slf4j
public class TestCallable implements Callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
TestCallable testCallable = new TestCallable();
Future future = executorService.submit(testCallable);
log.info("future: " + future.get()); //get会阻塞
executorService.shutdown();
}
@Override
public String call() throws Exception {
return "带含有返回值的线程";
}
}
接下来,我们 运行下TestCallable,看下结果
Connected to the target VM, address: '127.0.0.1:57004', transport: 'socket'
03:45:08.723 [main] INFO com.codecow.mini.test.TestCallable - future: 带含有返回值的线程
Disconnected from the target VM, address: '127.0.0.1:57004', transport: 'socket'
不难看出,看出啥了。。。。
后记
好啦,今就先聊到这里吧,本文仅仅是 抛砖引玉 而已,浅聊了实现“多线程”的几种方式。
其次,假如我在两年前我能看到类似的文章,并且“多读书、多看报,少吹牛B,多睡觉”,也不至于与 “小杨” 擦肩而过。哎!两年了。
★★ 好文推荐 ★★
- 恕我直言,我怀疑你没怎么用过Stream流
- 答应我,别再if/else校验请求参数了可以吗
- 浅聊过滤器 + 拦截器 + JWT
- 微信小程序支付 + 公众号支付 (含源码)
- 微信公众号授权+获取用户信息+jwt登录 (含源码)
- 微信小程序授权+获取用户手机号+jwt登录 (含源码)
小声BB
- 中国传统文化,先仔细看,若有用,再点赞, 给自己一点思考的时间
- 更多幽默、风趣好文,尽在“ CodeCow ” , WX搜索 CodeCow,可以看看
《 Java如海,学习作舟,泛舟于海,方知Java之宽阔 》