关于java中的线程,大家一定不陌生,多线程让我们看起来代码是在同时执行,当然这很不严谨,对于单核cpu,多线程只不过是jvm合理的分配时间片,轮流执行每一个线程的任务而已。
Thread(线程),在java中也是一个事物,那么在创建的时候我们可以,Thread thread = new Thread();来创建一个线程,而要想执行该线程,我们必须调用它的run()方法来让线程里的代码跑起来。如下代码:
Thread thread = new Thread();
thread.run();
显然要想执行我们的逻辑,run()方法的指令内容是至关重要的,
下面让我们来看一下Thread的run()方法,
@Override
public void run() {
if (target != null) {
target.run();
}
}
我们发现,其中有一个target变量,当这个target不为null的时候,执行的是target的run方法。而target又是什么呢?
/* What will be run. */
private Runnable target;
我们发现这个target实际是Runnable,也就是说,Runnable不为空的情况下,当调用Thread的run方法时,执行的是Runnable接口的run,(Thread是实现了Runnable接口)
好了,这样看来,Thread的run方法也没什么,我Runnable为null的时候,什么都没干。这样是没什么意义的,因此我们一般在创建线程的时候一般是自定义一个thread去继承Thread,重写它的run方法。
因此产生了创建Thread的第一种方式:
// TODO Auto-generated method stub
Thread thread = new Thread(){
@Override
public void run() {
while(true){
System.out.println("方式一"+Thread.currentThread().getName());
}
};
};
thread.start();
这里我用new匿名类的方式创建了Thread的子类,然后重写了它的run方法,并打印它的线程名。
第二种方式就是通过Thread的一个构造方法,传一个Runnable对象来创建线程,先看源码:
*
* @param target
* the object whose {@code run} method is invoked when this thread
* is started. If {@code null}, this classes {@code run} method does
* nothing.
*/
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
显然我们知道Runnable是一个接口,所以我们可以通过以下代码来创建线程:
//通过Thread类的一个构造创建
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("方式二"+Thread.currentThread().getName());
}
});
thread2.start();
}
好了,这就是Thread常用的两种创建方式。这个最简单基础不过了,下面我们通过这两种创建方式引出一个小问题来探讨一下,
先看代码:
/**
* 由线程创建两种方式引出来的一个小问题
* 思考:当我通过Runnable构造创建Thread时,Thread子类重写了run方法,
* 同时Runnable子类也重写了run方法。
* 我执行该thread时,走的是谁的run方法?
*/
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("Runnable的子类的run方法执行");
}
}){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("Thread子类的run方法执行");
}
};
thread3.start();
这里我复述一遍问题,可以看到,我是通过Runnable构造的方式来创建的Thread,而此时,我的Thread子类跟Runnble同时重写了各自的run方法,我想知道,当这个线程开启的时候代码是如何执行的。
先来看结果:结果是....Thread子类的run方法执行
那我们来思考一下,为什么Runnable子类的run方法没有执行呢,为什么会只执行Thread子类的run。
在文章的一开始,我们了解过了Thread的run方法的源代码,再来看一眼
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
这里的target是一个Runnable类型的对象。
这里,我们先拉个外传,看看这个target的run到底是怎么执行进来的,也就是Runnable的run方法是如何被执行的。target是Thread的一个Runnable类型的成员变量,
/* What will be run. */
private Runnable target;
可知,这种run执行是线程创建的第二种方式,所以我们来看看构造
* @param target
* the object whose {@code run} method is invoked when this thread
* is started. If {@code null}, this classes {@code run} method does
* nothing.
*/
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
这种创建形式实际是调用了init方法,我们巡着来看下init方法
/**
* Initializes a Thread with the current AccessControlContext.
* @see #init(ThreadGroup,Runnable,String,long,AccessControlContext)
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null);
}
这里四个参数的init调用了五个参数的init,那我们再看就是了,看看到底这个Runnable是怎么传进来并执行的。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
Thread parent = currentThread();
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();
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();
this.target = target;
setPriority(priority);
if (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();
}
好吧,为了看的清楚,我把整个方法的逻辑都拷了过来,但是,我们这里的重点仅仅看我标红加粗的那一句即可。哈哈,太坑了吧!
但是,这就太明显了,当我通过Runnable构造来创建Thread的时候,我通过传递一个Runnable接口子类参数,名字叫target,第一层调用四个参数的init方法,然后传入调用五个参数的init方法,在该方法中我们终于把它赋值给了,Thread类的那个Runnable类型的成员变量target,而该target是否为空又是run方法到底怎么执行的关键。
言归正传,显然,该讨论问题的Runnable子类参数不为空,就是target不为空,那不是应该执行的事Runnable的run方法吗,怎么最后结果输出的确实Thread的run方法?
哈哈,卖了这么大的关子,我们千万不能被绕进去,这里,我们是自定义了一个Thread的子类,new Thread();该子类中同样重写了run方法,根据多态的原理,只有非静态的成员方法,我们是编译看左边,运行看右边。什么意思?就是说我这里子类重写了Thread类的run方法,编译器编译的时候找的是父类的run方法,我只要看看父类有没有这个方法,有,我就放行,编译通过,父类若没有run方法,我就编译不通过,抛出异常。而这里实际执行是new Thread()这个子类的run方法。也就是说,我这里重写了你的方法,跟你Thread的run半毛钱关系都没有,因此,自然就不会做target的非空判断,自然就不会走Runnable接口子类的run方法。因此执行的是Thread子类的run方法。