Java 线程详解(上)

导读:本篇文章讲解 Java 线程详解(上),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

线程作为CPU调度的最小单位,它属于程序进程的子集,关于程序进程和线程的介绍,可以参考《详解操作系统进程》《详解操作系统线程》两篇文章,这篇文章主要介绍Java线程的相关原理。

一、线程创建

从实现上来说,Java提供了三种创建线程的方式,但从原理上来看,其实只有一种方式,我们先从实现上来简单介绍一下这三种方式

1.1 继承Thread类

直接创建一个ThreadTest的实例,调用它的start()方法就可以创建一个线程了

class ThreadTest extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

1.2 实现Runnable接口

如果只是简单的实现了Runnable接口,它与线程并没有任何关系,只是相当于创建了一个线程执行的任务类而已,要想真正的创建线程,还是需要创建一个Thread对象,把RunnableTest实例作为构造方法的入参

class RunnableTest implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

public class CreateThreadTest {

    public static void main(String[] args) {
        RunnableTest runnableTest = new RunnableTest();
        Thread thread = new Thread(runnableTest);
        thread.start();
    }
}

1.3 实现Callable接口

与Runnable很相似,它相当于也是也个任务的实现类,需要结合线程池的submit()方法才能使用,但与Runnable最本质的区别是,Callable的call()方法可以有返回值

class CallableTest implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        return ThreadLocalRandom.current().nextInt();
    }
}

public class CreateThreadTest {

    public static void main(String[] args) {
        CallableTest callableTest = new CallableTest();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Future<Integer> future = executorService.submit(callableTest);
    }

}

1.4 Lambda表达式

这种方式与第二种方式其实是一样的,只是写法比较简洁明了

Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));

二、线程实现原理

2.1 只有一种线程创建方式

我们前面说过从实现原理上来讲,创建线程只有一种方式,我们从源码上来分析这种说法

继承Thread和实现Runnable接口两种方式本身就是一种方式,通过创建Thread实例,然后调用start()方法来创建实例

我们先主要看一下Callable接口实现类的使用,我们具体看一下ExecutorService的submit()方法

在submit()方法中,首先将Callable实例封装成一个FutureTask实例,FutureTask实现了RunnableFuture接口,而RunnableFuture又实现了Runnable接口,也就是说封装后的FutureTask仍然只是一个任务实例,此时与线程并没有任何关系,真正建立关系是在execute()方法中

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

execute()方法是线程池的核心方法,该方法在后面介绍线程池的文章中会对其进行详细介绍,现在我们主要看它的addWorker()方法,该方法就是去创建一个线程

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    ……
}

在addWorker()方法中,会去创建一个Worker实例,而在Worker的构造方法中,会去创建一个Thread实例

private boolean addWorker(Runnable firstTask, boolean core) {
    ……
    w = new Worker(firstTask);
    final Thread t = w.thread;
    ……
}

首先会去拿到一个ThreadFactory实例,我们以DefaultThreadFactory为例,看下newThread()方法的实现,就是去创建了一个Thread实例

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

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;
}

总结:从上面对Callable的分析,我们可以得出结论,所有创建线程的方式都可以归结为一种方式,那就是创建Thread实例

那么问题就来了,我们创建了一个Thread实例,就完成了线程的创建吗?那我们的run()和start()方法又有什么区别呢?带着这样的问题,我们深入JVM和操作系统层面,来看一个Java线程创建的过程到底是什么样的

2.2 线程创建原理

2.2.1 run()与start()

首先我们先看一下run()和start()方法的区别,如果我们自定义一个类ThreadTest,然后继承了Thread类,可以选择是否重写run()方法,这个时候,我们创建了一个ThreadTest的实例,当我们用这个实例去调用run()方法时,这就是一个简单的方法调用,与线程没有任何关系

class ThreadTest extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

同样如果我们实现了Runnable接口,先创建一个Runnable接口的实例,然后作为构造方法入参创建一个Thread实例

class RunnableTest implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

public class CreateThreadTest {

    public static void main(String[] args) {
        RunnableTest runnableTest = new RunnableTest();
        Thread thread = new Thread(runnableTest);
    }

}

在构造方法中会去调用init()初始化方法,初始化方法中把Runnable的实例存在了Thread实例的target属性中

当调用Thread实例的run()方法时,就是简单的去调用Runnable实例的run()方法,也与线程的创建没有关系,只是普通的方法调用

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    ……
    this.target = target;
    ……
}

public void run() {
    if (target != null) {
        target.run();
    }
}

下面我们看一下start()方法的源码,在start()方法中,会去调用本地方法start0(),这个方法才是真正去创建一个线程

public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}

private native void start0();

2.2.2 线程创建流程

在Thread初始化的时候,首先会去调用本地方法registerNatives(),这个方法的主要作用是绑定线程相关的本地方法和真正JVM方法之间的映射关系

public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }
}

JNINativeMethod中建立了JNI的映射关系

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/112131.html

(0)
Java光头强的头像Java光头强

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!