多线程详解(1) 您所在的位置:网站首页 thread启动方法 多线程详解(1)

多线程详解(1)

#多线程详解(1)| 来源: 网络整理| 查看: 265

1、Runnalbe接口和Thread类有什么关系

通过源代码可以发现Thread类实现了Runnable接口,并且重写了Runnable接口中唯一定义的run() 方法

public interface Runnable { public abstract void run(); } public class Thread implements Runnable { ... } 复制代码

在Java中,interface定义的是一种规范,如果某个类实现了这个这个接口,那么他一定要(或者在子类中)去实现这个interface中所规定的方法,也就是去重写这个方法。

Runnable接口定义了Java中所有的线程类都应该具有的方法,即run() 方法。也就是如果要实现一个线程类,必须要继承Runnable接口,并重写其中的run() 方法。但是这个方法是无返回的,如果当前线程执行完毕之后需要返回一些内容给请求方,可以使用Callable接口,这个后面会详细探讨,Callable接口的实现类最终也会被包装成Runnable接口实现类的一个属性。

Thread类实现了Runnable接口,对run方法进行了重写,同时支持了和线程相关的其他内容,比如线程的启动、结束、状态管理等方法。我们通过实现Runnable接口所定义的线程类只能通过Thread类才可以成功启动一个线程进行执行。Thread类是Java提供给编程人员启动一个线程的入口,只有通过Thread类的start方法,才可以成功启动一个新的线程。

所以,Runnable接口和Thread类的关系就是,Runnable接口定义了Java中线程类的实现规范,Thread类实现了Runnable接口,并提供了关于线程创建、启动、停止等相关的方法。

2、创建、启动线程的两种方式

线程的创建本质上线程类是要实现Runnable接口,并重写run方法,启动只能通过Thread类的start方法进行启动。

那么我们可以得到两种创建线程的方式:

实现Runnable接口,并将这个实现类的实例作为参数传递给Thread类的构造方法中 继承Thread类,间接的实现Runnable接口,重写run方法 // 方式1:通过实现Runnable接口 public class MyThread implements Runnable { ... @Override public void run() { ... } } Thread thread = new Thread(new MyThread()); thread.start(); // 也可以使用表达式+匿名类的形式 new Thread((() -> { System.out.println(Thread.currentThread().getName()); })).start(); // 方式2: 通过继承的方式来定义一个线程类 public class MyThread extends Thread { @Override public void run() { ... } } Thread thread = new MyThread(); thread.start(); 复制代码

方式2的创建启动方式很容易理解,我们创建的MyThread类是Thread类的子类,在调用的时候其实调用的是父类的start方法,进而完成的线程的启动。

方式1的创建启动方式稍微特殊一点,我们传入的是一个Runnable接口的实现类,那么Thread类是如何保证线程启动后,执行的是我们传入的实现类的run方法的呢,原因就在于线程的启动创建的过程中。

3、线程的创建启动过程

首先看一下线程的创建部分的代码,Thread类有个多个构造方法,主要是由于传入参数的不同,但是最终调用的都是init方法,我们这里主要看一下init方法即可。

// 方式1调用的构造方法 public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } // 所有构造方法最终都会调用到这个方法上 private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } // 完成线程名字的赋值 this.name = name; // 获取父线程并处理安全相关的内容,暂时可以不看 Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); ... // 完成线程属性的赋值 this.group = g; this.daemon = parent.isDaemon(); // 是否是守护线程 this.priority = parent.getPriority(); // 优先级 if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); // 类加载器 else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = // 访问控制Context acc != null ? acc : AccessController.getContext(); this.target = target; // 传入的Runnable接口实现类 setPriority(priority); if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); // 继承ThreadLocalMap // 线程堆栈的大小,如果为0则表示忽略(有的虚拟机会忽略这个参数) this.stackSize = stackSize; // 设置线程id tid = nextThreadID(); } 复制代码

其参数的主要内容为:

g:线程group相关内容 target:target实例的run方法将在新创建的线程中被调用 name:线程名字 stackSize:新线程的堆栈的大小,如果为0则表示忽略(有的虚拟机会忽略这个参数) acc:访问控制相关 inheritThreadLocals:是否继承父类的threadLocals,为true则表示继承

通过上面的代码可以看到,通过方式1的形式创建一个线程的时候,我们编写的Runnable接口的实现类作为一个参数值保存在了Thread类的target属性中,同时启动一个线程的时候,是通过Thread类的start方法进行启动的,整个的调用过程是Thread.start() --> Thread.start0() --> Thread.run() ,那我们接下来主要看下这三段代码的具体内容。

public synchronized void start() { // ... 省略一些判断的代码 boolean started = false; try { start0(); // 重点:在start方法中又去调用了start0方法 started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } } // start0是一个native方法,也就是一个本地方法,是由Java虚拟机实现的 // 这个方法会启动一个新的线程,并在这个新线程中调用run方法 private native void start0(); // Thread类中重写的run方法,这个run方法真正的去调用了target中的run方法 @Override public void run() { if (target != null) { target.run(); } } 复制代码

根据上面的代码就可以明白方式1的启动中是如何调用到我们自己写的MyThread类的run方法了。

其实这个可以看成是一种静态代理的模式,Thread类在执行run方法前,完成参数的赋值,对parent线程的继承以及创建出一个新的线程等工作。我们编写的Runnable实现类就作为被代理的对象,在Thread的run方法中被真正的进行调用。

同理,方式2中的启动逻辑,通过重写了run方法,使得可以在Thread类的启动链里面直接执行到我们编写的线程run方法逻辑。

总结 Runnable 接口定义了线程类所应该具有的规范,即实现run方法 Thread 类实现了Runnable接口,并实现了线程启动的相关逻辑 线程创建启动有两种方式:1、参数传入Runnable接口的实现类; 2、继承Thread类并重写run方法 线程启动的方法调用链:Thread.start() -> Thread.start0() -> Thread.run() -> target.run()


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有