多线程并发基础(2)线程的实现

116 阅读4分钟

1. 线程创建方式

我看网上很多文章,对此答案结论不一,有说两种的也有说三种的,那么到底是几种呢?我们不妨去源码中找找答案。进入Thread类,看下类描述注释

image.png 发现其实它早已描述好了,只有两种方式

  • 一是继承Thread类,重写run方法,调用start方法开启线程
  • 二是实现Runnable接口,实现run方法,调用start方法开启线程

所以后面如果还有人问,到底有几种创建方式,你就可以大胆的告诉它:就两种,源码里有!

另外,稍微提一下,Thread也实现了Runnable接口,这涉及代理模式相关的知识,具体内容可以关注下后面的文章。ok,如果你不理解,也不要因为这段话而苦恼,当我啥也没说,我撤回!

2. 线程创建方式1——继承Thread类

细分的话,其实是三步

  1. 继承Thread
  2. 重写run方法
  3. 调用start方法开启线程

多说无益,看代码就很好理解

public class ThreadDemo {
    public static void main(String[] args) {

        CuteThread cuteThread = new CuteThread();
        cuteThread.start(); // 调用start方法开启线程

        // 这里是main线程
        for (int i = 0; i < 1000; i++) {
            System.out.println("我是main线程,我不可爱");
        }
    }
}

// 这个是新线程
class CuteThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("我是可爱的线程");
        }
    }
}

在这个例子中,我手动创建了一个线程,连同main线程总共就是两个线程。在这两个线程中,我都使用了for循环,分别执行了不同的输出语句,我们可以看下结果。

image.png 由于main线程打印内容非常多,所以就不全部展示了。只看关键的部分,在前期,main线程一直在输出,到了某一个时刻,我手动创建的CuteThread抢到了时间片,执行了run方法里面的内容,由于CPU执行速度过快,所以5次for循环一瞬间就执行完了,而后main线程继续抢到时间片,继续运行。

这就是第一种线程创建方式,继承Thread类。同时也初步感受了一下“并发”:我们在程序执行的这段时间内,是运行了两个线程,但在某一个时刻,只有一个线程在工作。

3. 线程创建方式2——实现Runnable接口

在看过第一种创建方式后,第二种创建方式就很好理解了 细分的话,可以说是四步

  1. 实现Runnable接口
  2. 实现run方法
  3. 创建Thread类,将实现了Runnable接口的对象作为构造函数的参数传入
  4. 调用Threadstart方法开启线程

直接看代码

public class ThreadDemo {
    public static void main(String[] args) {
        new Thread(new CuteThread()).start();

        // 这里是main线程
        for (int i = 0; i < 500; i++) {
            System.out.println("我是main线程,我不可爱");
        }
    }
}

// 这个是新线程
class CuteThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 500; i++) {
            System.out.println("我是可爱的线程");
        }
    }
}

运行结果和上面代码的结果是类似的,这里就不重复展示了。

4. 调用start和调用run是不一样的

这里还有个容易忽略的问题,就是为什么不调用run方法,而是调用start方法呢?两者有什么区别?

我总结下,其实调用start方法,才是真正开启线程,后面讲解线程状态的时候会说到,这里是进入了就绪状态,准备和其他线程一起争夺时间片。而直接调用run方法,就和调用不是线程类的其他类的某一个方法一样了,并没有真正开启一个线程。不相信的朋友也可以去试一下,把start方法改成run,那所有的程序执行顺序就和调用顺序一样了,run方法的内容执行完后才会执行后面的代码,不会出现“并发”的情况了。

5. 两种创建方式,如何去选择?

一般都会推荐第二种,即实现Runnable接口。

好处有以下2个

  1. Java是单继承多实现,使用第二种线程创建方式,可以避开这种局限性,保存类的扩展性
  2. 使用第二种线程创建方式,可以多个线程共享同一个对象,非常适合多个线程来处理同一份资源的情况

关于第二个好处的细节,后面的文章会进行解释。