为啥会有这篇文章
时光飞逝,回到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吗!
实现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);
}
}
}
其结果不言而喻,也是 “撸代码线程” 和 “看大片线程” 各自执行了五次
然并卵,同样在实际开发中,并非像上篇文章《答应我,别再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<String> {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
TestCallable testCallable = new TestCallable();
Future<String> 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'
不难看出,看出啥了。。。。欢迎留言讨论 ^_^