多线程的创建方式和相关面试题


在之前的博客文章中 我们了解到了。什么是多线程。如果有没看的小伙伴可以去看一看

https://blog.csdn.net/zyz0225/article/details/80753214

那么在本节中,就应该知道如何创建多线程。线程的创建包括定义线程体和创建线程对象两部分。线程体是由run()方法定义的。运行时,系统会自动调用run()方法来实现线程的具体功能的。


Thread类是存放在java.lang类库中的,但是在编写代码的时候不必刻意去加载java.lang类库,因为系统会自动加载。run()方法是Thread类里的一个方法,也是Runnable接口唯一的一个方法。当一线程被初始化之后,由start()方法来调用它,一旦run()方法返回,那么本线程也就终止了。子类可以通过继承Thread类而重写run()方法,事实上所做的就是覆盖操作。因此要创建一个多线程,必须按照下面的格式来编写:

public class 类名称 extends Thread{ // Thread类扩展出子类

public类名称(){

//编写子类的构造方法,可省略

}

   属性 //声明子类的数据成员

   方法 //定义子类自己的方法

public void  run(){

// 重写Thread类里的run()方法

   }

}

用继承Thread类的子类创建线程对象的一般格式为:

子类类名称 对象名 = new子类类名称();

例如:SubOfThreadClassname stcn = new SubOfThreadClassname();

启动该线程对象的格式为:子类的对象.start();

例如:stcn.start();

start()的作用是使该线程开始执行,Java 虚拟机调用该线程的run()方法

【代码剖析】应用继承类Thread的方法实现多线程的例子如下:

//文件名:TextDemo.java

class ThreadSubName extends Thread //创建Thread类的子类

private String threadName; //线程的名字(变量)

private int Ms; //休眠的毫秒数(变量)

public ThreadSubName(String name, int ms) { //子类的构造方法,为私有变量赋初值

this.threadName = name;

this.Ms = ms;

}

public void run() { //重写Thread类的run()方法

try {

sleep(Ms);

} catch (InterruptedException e) { //捕获sleep()的中断异常

System.out.println("The Thread is Wrong");

}

//打印出当前运行的线程的名字和休眠的时间

System.out.println("名字叫" + threadName + "  开始休眠" + Ms + "毫秒");

}

}

public class TextDemo {

public static void main(String[] args) {

ThreadSubName t1 = new ThreadSubName("Thread 1", 200); //创建线程对象t1

ThreadSubName t2 = new ThreadSubName("Thread 2", 100); //创建线程对象t2

ThreadSubName t3 = new ThreadSubName("Thread 3", 300); //创建线程对象t3

t1.start(); //启动t1线程

t2.start(); //启动t2线程

t3.start(); //启动t3线程

}

}

运行结果如下:

名字叫Thread 2  开始休眠100毫秒

名字叫Thread 1  开始休眠200毫秒

名字叫Thread 3  开始休眠300毫秒

【解释说明】如果抛开线程,就用以前学过的知识来预测这段代码的运结果,应该是先打印名字叫Thread 1开始休眠200毫秒”,最后打印出“名字叫Thread 3开始休眠300毫秒”。但是从代码的运行结果中可以看出,多线程并不是顺着程序的流程执行的。为什么会是这样的结果呢?主要的原因在sleep(Ms);这条语句上。

程序启动时总是会自动的调用main()方法,执行主线程,因此main()是创建和启动线程的地方。首先创建了t1t2t3三个线程并传入了相应的参数值,当程序运行到t1.start();启动线程并自动调用run()方法,在run()方法中,sleep(200)这个方法使t1线程暂时停止运行200毫秒,等200毫秒后,线程才可民恢复运行。在t1线程休眠的时间里,把执行权主动的交给了t2.而不是等t1恢复运行后再让t2运行。所以才会打印出上面的运行结果。


通过前面的学习想必大家都知道,在继承关系中Java规定一个子类只能有一个父类。但是在编写程序的过程中,会遇到各种各样的问题例如在Java中如果一个类继承了某一个类,同时又想采用多线程技

术的时,这时就不能釆用继承Thread类产生线程的方式了,因为Java不允许多继承,那怎么办呢?这时就可以釆用实现Runnable接口来解决这个问题。

Runnable接口来创建线程就是在一个类中实现Runnable接口,并在该类中重写run()方法。创建多线程的的一般格式为:

class 类名称 implements Runnable   // 实现Runnable接口

{

   属性

   方法

public 类名称(){//构造方法

}

  public void run(){            // 重写Runnable接口里的run()方法

//线程的功能代码

   }

}

创建线程的步骤如下:

1)生成实现Runnable接口的类的实例化对象。代码如下:

class RunnableSub implements Runnable

RunnableSub rs = new RunnableSub();

2)用带有Runnable参数类型的Thread类构造方法创建线程对象。代码如下:

Thread t = new Threadrs;

3)启动线程。代码如下:

t.start();

【代码剖析】应用实现Runnable接口的方法创建多线程的例子如下:

//文件名:TextDemo_1.java

class RunnableDemo extends ThreadRun implements Runnable {//创建一个Runnable的子类并且这个类也是ThreadRun的子类

Thread t2 = null; //创建全局变量

public void RDemo(RunnableDemo r1) { //创建一个RDemo方法

Thread t1 = new Thread(r1, "第一线程"); //创建线程对象t1

System.out.println("正在运行的是" + t1);

t2 = new Thread(r1, "第二线程"); //创建线程对象t1

System.out.println("创建第二线程");

System.out.println("第一线程开始休眠");

t2.start(); //启动t2线程

try {

t1.sleep(400); //t1线程休眠400毫秒

} catch (InterruptedException e) { //捕获异常

System.out.println("第一线程错误");

}

System.out.println("第一线程恢复运行");

}

public void run() { //重写Runnable接口的run()

try {

for (int i = 0; i < 800; i += 100) { //for循环设置休眠的时间

System.out.println("第二线程的休眠时间:  " + i);

t2.sleep(i);

}

} catch (InterruptedException e) {

System.out.println("第二线程错误");

}

System.out.println("第二线程结束");

}

}

class ThreadRun { // RunnableDemo类的父类

public String print() {

return "我是RunnableDemo的父类";

}

}

public class TextDemo_1 {

public static void main(String[] args) {

RunnableDemo r1 = new RunnableDemo(); //创建RunnableDemo的实例化对象

r1.RDemo(r1); //调用RDemo(RunnableDemo r1)方法

System.out.println(r1.print());

}

}

运行结果如下:

正在运行的是Thread[第一线程,5,main]

创建第二线程

第一线程开始休眠

第二线程的休眠时间:  0

第二线程的休眠时间:  100

第二线程的休眠时间:  200

第二线程的休眠时间:  300

第一线程恢复运行

我是RunnableDemo的父类

第二线程的休眠时间:  400

第二线程的休眠时间:  500

第二线程的休眠时间:  600

第二线程的休眠时间:  700

第二线程结束

【解释说明】这段代码首先就说明了用Runnable接口创建线程是可以解决类多继承这个难点的。因为RunnableDemo这个类不仅实现了Runnable接口同时也继承了ThreadRun类。

在创建线程对象的时候要注意,RunnableDemo r1 = new RunnableDemo();虽然RunnableDemo是Runnable的子类,但是创建的r1并不是线程对象,只是一个普通的类对象。创建真正的线程对象是必须用带有Runnable参数的Thread类创建的对象,例如Thread t1 = new Thread(r1"第一线程");t1就是线程对象。在Thread类中带有Runnable接口的构造方法有(可以参考JDKAPI):

q public Thread(Runnable target) 

q public Thread(ThreadGroup group,Runnable target)

q public Thread(Runnable target,String name) 

q public Thread(ThreadGroup group,Runnable target,String name)

这些构造方法都是分配新的Thread对象的作用其中Runnable target表示run()方法被调用的对象ThreadGroup group表示线程组String name表示新线程的名称。线程的运行流程同继承Thread类的一样,可以参考继承Thread类的代码分析。

【考题题干】创建线程有哪两种方式?它们的区别是什么?

【参考答案】有两种实现方法,分别是继承Thread类与实现Runnable接口。

实现Runnable接口除了拥有和继承Thread类一样的功能以外,实现Runnable接口还具有以下功能。

q 适合多个相同程序代码的线程去处理同一资源的情况,可以把线程同程序中的数据有效的分离较好地体现了面向对象的设计思想。

可以避免由于Java的单继承特性带来的局限。例如,class Student已经继承了class Person,如果要把Student类放入多线程中去,那么就不能使用继承Thread类的方式。因为在Java中规定了一个类只能有一个父类,不能同时有两个父类。所以就只能使用实现Runnable接口的方式了。

增强了代码的健壮性,代码能够被多个线程共同访问,代码与数据是独立的。多个线程可以操作相同的数据,与它们的代码无关。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。

.4  面试题4:选择出正确的答案

【考题题干】关于Threads哪些描述是正确的?

A.线程可以创建唯一的子类java.lang.Thread

B.调用suspend()方法可以使线程终止并且无法再启动它。

C.程序的执行完毕是以用户线程的结束而标志结束的,与超级线程无关。

D.不同线程对相同数据进行访问时,可能造成数据毁损。

【参考答案】CD

猜你喜欢

转载自blog.csdn.net/zyz0225/article/details/81036999