概要
线程作为独立调度运行和分派的基本单位,很明显的一个特征便是可运行的。Runnable接口便是对可被调度运行进行了抽象声明。本章主要内容如下:
- 线程构造函数介绍
- Thread和Runnable的区别与联系
- 创建线程示例
线程构造函数介绍
/**
* 无参构造函数,常用
*/
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
/**
* 需要参数Runnable对象构造函数,常用
*/
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
/**
* 线程名称,常用
*/
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
/**
* 常用
*/
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
创建线程的时候常用无参和设置线程名、设置Runnable对象及组合的方式。常见的有Thread()、Thread(Runnable target)、Thread(String name)和Thread(Runnable target, String name),设置线程组的方式比较少见,除非真的有必要进行分组管理,系统启动的时候会设置系统线程组、main线程组。
线程init初始化方法
java.lang.Thread
/**
* Initializes a Thread.
*
* @param g 线程组
* @param target 可被调度运行对象
* @param name 线程名
* @param stackSize 线程堆栈大小,0表示忽略
* @param acc 线程内部访问控制
* @param inheritThreadLocals 是否继承线程本地变量
*/
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();
//默认情况下jvm不开启安全管理器
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
//不是通过设置线程组的方式创建线程,默认归属于父线程的线程组
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
//线程组赋值
this.group = g;
//是否守护线程继承默认继承自父线程
this.daemon = parent.isDaemon();
//线程优先级,也是继承自父线程,默认为5
this.priority = parent.getPriority();
//线程对应类加载器
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
//Runnable实例,相当于要运行的task
this.target = target;
setPriority(priority);
//继承父线程inheritThreadLocals
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
除了安全管理器的访问检查稍微陌生(自行baidu、google),线程初始化相对比较简单,做了线程相关属性的初始化操作。
Thread和Runnable的区别与联系
Runnable是接口,用于声明Runnable实例是可以被线程调度运行的,只定义了一个run()方法:
public interface Runnable {
public abstract void run();
}
可以通过public Thread(Runnable target)、public Thread(Runnable target, String name)构造函数进行创建线程。
Thread是类,实现了Runnable接口,这也说明了线程是可以被独立调度运行和分派的。
public class Thread implements Runnable {
...
/**
* 实现Runnable,线程CPU被调度后代码执行入口
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
...
}
从代码上看,直接通过无参构造函数创建线程并启动,实际上run()就相当于一个空方法。因此,如果不以Runnable实例创建线程,则应该通过继承Thread类覆盖run()方法的方式创建线程。接下来是常见的创建线程的两种方式。
创建线程示例
- 继承Thread类覆盖run()方法的方式
public class Hello extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Hello word !");
}
}
//调用
public class ThreadCreate {
public static void main(String[] args) {
//启动三个线程
for (int i = 0; i < 3; i++) {
//一定要注意的是调用start()方法启动线程,而非调用run()方法
new Hello().start();
}
}
}
运行结果:
Thread-0 Hello word !
Thread-2 Hello word !
Thread-1 Hello word !
- 实现Runnable方式创建线程
上边的代码修改为实现Runnable方式:
public static class Hello implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Hello word !");
}
}
public class ThreadCreate {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(new Hello()).start();
}
}
}
运行结果:
Thread-0 Hello word !
Thread-2 Hello word !
Thread-1 Hello word !
选择哪种方式
通过继承的方式,Thread实现Runnable接口,即Thread是Runnable实例。从Thread实现Runnable接口代码片段看,如果target(Runnable实例)为空,run()相当于是一个空方法,继承覆盖的方式实际上就是重新实现了Runnable接口。
target被多个线程共享,同一个任务由多个线程完成,发挥多线程的优势。拿Thread当Runnable实例感觉上总是有些别扭。
所以,从面向接口编程的角度看推荐直接实现Runnable的方式创建线程。