本文通过几个问题入手,主要通过案例讲解Thread类的使用,以及注意事项,最后站在源码的角度分析问题。
1.多线程一定快吗?
多线程不一定快!
多线程只是极大限度的利用CPU的空闲时间来处理其他的任务,这样才会缩短多个任务的执行时间。如果在没有CPU空闲的情况下,多线程之间的上下文切换(保存线程的运行环境,还原线程的运行环境)会产生开销,执行时间会慢于单线程。
2.区分单任务操作系统与多任务操作系统
单任务操作系统:同步执行,排队。只有当前任务执行完了才可以执行下一个任务。例如cmd中执行完一条指令之后才可以执行另一条指令
多任务操作系统:异步执行,通过时间片的快速切换实现多个任务响应执行。
3.创建Thread类的两种形式,及其差别
(1)继承Thread类,重写run( )方法
package com.feng.example;
public class MyThread extends Thread {
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
System.out.println("继承Thread类");
}
}
启动线程:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread myThread = new MyThread();
myThread.start();
}
}
(2)创建一个Runnable接口的实现类A,将A的实例对象作为Thread类的参数传入
package com.feng.example;
public class MyRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("通过实现Runnable接口重写run");
}
}
启动线程:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Runnable myRunnable = new MyRunnable();
Thread myThread = new Thread(myRunnable);
myThread.start();
}
}
由于在Thread类也是实现Runnable接口的,因此Thread的对象也可以作为第二种形式的参数
public class Thread implements Runnable {} //这里只是贴出源码中Thread类的定义
将第一种方式创建的MyThread类对象作为第二种方式的输入参数
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建MyThread类对象
Runnable myRunnable = new MyThread();
Thread myThread = new Thread(myRunnable);
myThread.start();
}
}
两种方式的区别:两种方式没有本质的区别,一般都会使用第二种形式,主要解决java中类不能多继承的缺陷。
比如Cat类继承Animal,同时又想将Cat定义为线程类,因为Cat不能同时继承Animal,Thread类,但是Cat可以继承Animal,实现Runnable接口
4.通过线程的start( )方法运行run( )方法中的代码与直接调用run( )方法的区别
回顾一下操作系统课中线程的创建过程:线程的创建包括两部分
(1)创建线程,相当于MyThread myThread = new MyThread( );
(2)将线程放到就绪队列中,如果仅仅只是创建了线程却没有将线程放到就绪队列中,线程调度器是没法找到线程的。因此myThread.start( );将线程放到就绪队列中。等待分配cpu时间片,当获取了cpu之后就可以执行线程的run( )方法
通过start( )方法:是新开辟了一个线程,run( )方法是由线程规划器来调用的,是异步运行
直接调用run( )方法:调用者是Thread子类的一个对象,属于同步执行,没有开辟新的线程。
package com.feng.example;
public class MyThread extends Thread {
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
System.out.println("线程为:"+Thread.currentThread().getName());
}
}
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建MyThread类对象
MyThread myThread = new MyThread();
myThread.start();
myThread.run(); //同步调用run方法
}
}
程序运行结果:
5.两种实现方式一块使用,会调用那种方式的run( )方法,通过源码分析
首先查看Thread类源码中是如何重写Runnable接口中的run( )方法的。
public void run() {
if (target != null) {
target.run();
}
}
接下来看一下这个target又是什么?
/* What will be run. */
private Runnable target;
由源码可以看出,在Thread类的内部,存在一个类型为Runnable的成员变量,此成员变量便是接收第二种创建线程类的方法中的输入参数的。再看Thread类的run方法,只是判断有此target存不存在,如果存在便执行target的run( )方法。
如果不存在怎么办?
如果不存在则是使用继承的方式来实现的线程类,那么run( )方法已经在Thread子类中进行了重写,覆盖了父类的run( )方法
下面通过一个题目来消化这个知识点:
Runnable接口的实现类如下:
package com.feng.example;
public class MyRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("通过Runnable接口实现线程类");
}
}
Thread的子类实现如下:
package com.feng.example;
public class MyThread extends Thread {
public MyThread(Runnable target)
{
super(target);
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("通过继承实现线程类");
}
}
测试代码如下:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建Runnable接口实现类
Runnable myRunnable = new MyRunnable();
//创建MyThread类对象
MyThread myThread = new MyThread(myRunnable);
//到底输出的是谁的run方法????
myThread.start();
}
}
分析: 首先start的是MyThread线程,因为会执行MyThread类的run( )方法。虽然传入了Runnable类型的参数,但是在MyThread类中的run( )方法中并没有super.run( );因此程序只输出MyThread类中的run( )方法中的输出。
输出结果如图:
修改MyThread类中的run( )方法:
package com.feng.example;
public class MyThread extends Thread {
public MyThread(Runnable target)
{
super(target);
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
System.out.println("通过继承实现线程类");
}
}
分析:
首先start的是MyThread线程,因为会执行MyThread类的run( )方法。因为在构造MyThread时传入了Runnable类型的参数,因此在执行MyThread类中的run( )方法时,先执行super.run( ); 执行Thread类的run( ); 根据源代码先检查target存不存,这里target就是存入的myRunnable, 所以执行myRunnable里面的run( );执行完super.run( )方法后,在输出后面的语句。
输入结果如下图:
在有些地方可能会以匿名类的形式来进行考察:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("Runnable...");
}
}){
public void run()
{
//super.run();
System.out.println("Thread...");
}
}.start();
}
}
看到此代码不要惊慌,其实与我最初定义的三个类文件的考察方式是一样的,只不过这里使用了匿名类,如果忘记了回去翻看匿名类的知识,此题注意注释的那一句super.run( ); 看一下注释不注释有什么区别。
只要理解Thread源码中的run( )方法,这个题目就显得很简单了。