在之前的博客文章中 我们了解到了。什么是多线程。如果有没看的小伙伴可以去看一看
https://blog.youkuaiyun.com/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()是创建和启动线程的地方。首先创建了t1,t2,t3三个线程并传入了相应的参数值,当程序运行到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 Thread(rs);
(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接口的构造方法有(可以参考JDK.API):
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 适合多个相同程序代码的线程去处理同一资源的情况,可以把线程同程序中的数据有效的分离,较好地体现了面向对象的设计思想。
q 可以避免由于Java的单继承特性带来的局限。例如,class Student已经继承了class Person,如果要把Student类放入多线程中去,那么就不能使用继承Thread类的方式。因为在Java中规定了一个类只能有一个父类,不能同时有两个父类。所以就只能使用实现Runnable接口的方式了。
q 增强了代码的健壮性,代码能够被多个线程共同访问,代码与数据是独立的。多个线程可以操作相同的数据,与它们的代码无关。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
.4 面试题4:选择出正确的答案
【考题题干】关于Threads哪些描述是正确的?
A.线程可以创建唯一的子类java.lang.Thread。
B.调用suspend()方法可以使线程终止并且无法再启动它。
C.程序的执行完毕是以用户线程的结束而标志结束的,与超级线程无关。
D.不同线程对相同数据进行访问时,可能造成数据毁损。
【参考答案】CD