首页 为何说只有一种实现线程的方法
文章
取消

为何说只有一种实现线程的方法

进程是什么,线程是什么

进程是运行在内存中的程序,线程是进程中程序执行的基本单元

为什么有了进程,还要引入线程呢?

进程的上下文切换成本过高,为了降低并发导致的进程切换成本,提出了线程,线程几乎不占资源,因此线程切换的成本较少,后来就换用线程抢占cpu执行权

线程的创建方式

继承Thread类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ThreadDemo1 extends Thread {
    public static void main(String[] args) {
        // ThreadDemo1继承了Thread类,并重写run()
        ThreadDemo1 t = new ThreadDemo1();
        // 开启线程:t线程得到CPU执行权后会执行run()中的代码
        t.start();
    }

    @Override
    public void run() {
        System.out.println("Thread is running");
    }
}

该种方法继承Thread类,并重写run()方法

实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ThreadDemo2 implements Runnable{
    public static void main(String[] args) {
        // ThreadDemo2实现Runnable接口,并实现run()
        ThreadDemo2 target = new ThreadDemo2();
        // 调用Thread构造方法,传入TreadDemo2的实例对象,创建线程对象
        Thread t = new Thread(target);
        // 开启线程:t线程得到CPU执行权后会执行run()中的代码
        t.start();
    }

    public void run() {
        System.out.println("Thread is running");
    }
}

这种方法需要实现Runnable接口(Runnable接口只有一个run()方法)将实现类对象传递给Thread构造函数,有Thread对象调用Runnable实现类中的run方法

相较于直接继承Thread类覆盖run方法,我们常使用第二种方法

线程池创建线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

这段代码是java.util.concurrent下的一段源代码:

  1. 对于线程池而言,本质上是通过线程工厂创建线程的,默认采用 DefaultThreadFactory
  2. 会给线程池创建的线程设置一些默认值,比如:线程的名字、是否是守护线程,以及线程的优先级等
  3. 但是无论怎么设置这些属性,最终它还是通过 new Thread() 创建线程的 ,只不过这里的构造函数传入的参数要多一些,由此可以看出通过线程池创建线程并没有脱离最开始的那两种基本的创建方式,因为本质上还是通过 new Thread() 实现的。

有返回值的 Callable 创建线程

1
2
3
4
5
6
7
8
9
10
class CallableTask implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return new Random().nextInt();
    }
}
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//提交任务,并用 Future提交返回结果
Future<Integer> future = service.submit(new CallableTask());

这种线程创建方式是通过有返回值的 Callable 创建线程,Runnable 创建线程是无返回值的,而 Callable 和与之相关的 Future、FutureTask,它们可以把线程执行的结果作为返回值返回,如代码所示,实现了 Callable 接口,并且给它的泛型设置成 Integer,然后它会返回一个随机数。

但是,无论是 Callable 还是 FutureTask,它们首先和 Runnable 一样,都是一个任务,是需要被执行的,而不是说它们本身就是线程。

它们可以放到线程池中执行,如代码所示, submit() 方法把任务放到线程池中,并由线程池创建线程,不管用什么方法,最终都是靠线程来执行的

实现线程只有一种方式

  1. 首先,启动线程需要调用 start() 方法,而 start() 方法最终还会调用 run() 方法
  2. 而不管是实现Runnable接口还是基础Thread类,最终都需要调用new Thread().start()方法启动线程,而start()方法最终也会调用已经被重写或者说接口中实现的的run()方法来执行它的任务

所以说,事实上创建线程只有一种方式:就是构造一个Thread类,这是创建线程的唯一方式

为什么实现Runnable接口是创建线程的常用方法

  1. 限制Thread构造函数的参数类型,如果使用这种方法就必须向Thread构造函数传递Runnable实现类对象
  2. 解耦,将被执行的run方法视作资源,Thread对象视作执行者,第二种方法就是为了将执行者和资源进行解耦,第一种方法资源依然在Thread类中,而第二种方法资源是在Runnable的实现类中,这样就方便其他线程共享该资源

总结

无论是继承Thread类还是实现Runnable接口,线程的入口都是Thread类(因为Thread类的run方法在线程启动时一定会执行),下面的Thread类run方法的源码:

Thread类的run方法实现

本文由作者按照 CC BY 4.0 进行授权