一、为啥会有这篇文章

时光飞逝,回到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'

不难看出,看出啥了。。。。

后记

好啦,今就先聊到这里吧,本文仅仅是 抛砖引玉 而已,浅聊了实现“多线程”的几种方式。

其次,假如我在两年前我能看到类似的文章,并且“多读书、多看报,少吹牛B,多睡觉”,也不至于与 “小杨” 擦肩而过。哎!两年了。

作者:CodeCow

链接:https://juejin.im/post/6856813848322965512

来源:掘金