b站视频
133-多线程-线程创建方式1:继承Thread类_哔哩哔哩_bilibili
目录
3.1 继承Thread
3.1.1 继承Thread类方式
3.1.2 线程的执行流程
3.1.3 线程内存图
3.1.4 run()方法和start()方法
3.1.5 线程名字的设置和获取
3.1.6 获取运行main方法线程的名字
3.1.7 练习题
3.2 实现 Runable
3.2.1 实现Runnable接口方式
3.2.2 Thread和Runnable的对比
3.2.3 匿名内部类方式创建线程
Java使用java.lang.Thread
类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下:
定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
创建Thread子类的实例,即创建了线程对象
调用线程对象的start()方法来启动该线程
自定义线程类:
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("自定义线程正在执行!"+i);
}
}
}
测试类:
public class Main {
public static void main(String[] args) throws Exception{
MyThread mt=new MyThread();
mt.start();
for (int i = 0; i < 20; i++) {
System.out.println("main线程"+i);
}
}
}
结果:
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。
通过这张图我们可以很清晰的看到多线程的执行流程,那么为什么可以完成并发执行呢?我们再来讲一讲原理。
多线程执行时,到底在内存中是如何运行的呢?以上个程序为例,进行图解说明:
多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。
当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。
run()方法,是线程执行的任务方法,每个线程都会调用run()方法执行,我们将线程要执行的任务代码都写在run()方法中就可以被线程调用执行。
start()方法,开启线程,线程调用run()方法。start()方法源代码中会调用本地方法start0()来启动线程:private native void start0()
,本地方法都是和操作系统交互的,因此可以看出每次开启一个线程的线程都会和操作系统进行交互。
注意:一个线程只能被启动一次!!
实例:
package test;
public class TestDemo03 {
public static void main(String[] args) {
//创建当前Thread的子类的对象
PrintNumber t1=new PrintNumber();
//通过对象调用start()
t1.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
class PrintNumber extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
结果:
问题1:能否用t1.run()方法替换t1.start()的调用,实现分线程的创建和调用。
package test;
public class TestDemo03 {
public static void main(String[] args) {
//创建当前Thread的子类的对象
PrintNumber t1=new PrintNumber();
//通过对象调用start()
// t1.start();
t1.run();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
class PrintNumber extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
结果:(输出的全是main,通过调用run方法后变成了单线程)
Thread类的方法String getName()
可以获取到线程的名字。
Thread类的方法setName(String name)
设置线程的名字。
通过Thread类的构造方法Thread(String name)
也可以设置线程的名字。
public class MyThread extends Thread{
public void run(){
System.out.println("线程名字:"+super.getName());
}
}
测试类:
public class Demo {
public static void main(String[] args) {
//创建自定义线程对象
MyThread mt = new MyThread();
//设置线程名字
mt.setName("旺财");
//开启新线程
mt.start();
}
}
注意:线程是有默认名字的,如果我们不设置线程的名字,JVM会赋予线程默认名字Thread-0,Thread-1。
Demo类不是Thread的子类,因此不能使用getName()方法获取。
Thread类定义了静态方法static Thread currentThread()
获取到当前正在执行的线程对象。
main方法也是被线程调用了,也是具有线程名字的。
public static void main(String[] args){
Thread t = Thread.currentThread();
System.out.println(t.getName());
}
结果:
创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数。
package test;
public class TestDemo03 {
public static void main(String[] args) {
//创建当前Thread的子类的对象
PrintNumber t1=new PrintNumber();
//创建当前Thread的子类的对象
PrintOddNumber t2=new PrintOddNumber();
//通过对象调用start()
t1.start();
//通过对象调用start()
t2.start();
}
}
//遍历100以内的偶数
class PrintNumber extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0) {
System.out.println(Thread.currentThread().getName() +"--" + i);
}
}
}
}
//遍历100以内的奇数
class PrintOddNumber extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==1) {
System.out.println(Thread.currentThread().getName() +"--"+i);
}
}
}
}
结果:
采用java.lang.Runnable
也是非常常见的一种,我们只需要重写run方法即可。
步骤如下:
创建一个实现Runnable接口的类
实现接口中的run()方法 --> 将此线程要执行的操作,声明在此方法体中
创建Runnable实现类的实例
将此对象实例作为参数传递到Thread类的构造器中,创建Thread类的实例
Thread类的实例调用start()方法:①启动线程 ②调用当前线程的run()。
package test;
public class TestDemo04 {
public static void main(String[] args) {
//③ 创建当前实现类的对象
EvenNumberPrint evenNumberPrint=new EvenNumberPrint();
//④ 将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
Thread t1=new Thread(evenNumberPrint);
//⑤ Thread类的实例调用start():1.启动线程 2. 调用当前线程的run()方法
t1.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
//① 创建一个实现Runnable接口的类
class EvenNumberPrint implements Runnable{
//② 实现接口中的run() -->将此线程要执行的操作,声明在此方法中。
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
结果:
通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
共同点:
启动线程,使用的都是Thread类中定义的start()方法
创建的线程对象,都是Thread类或其子类的实例。
不同点:
一个是类的继承,一个是接口的实现。
建议:建议使用实现Runnable接口的方式
Runnable方式的好处:
①实现的方式,可以避免类的单继承的局限性。
②更适合多个相同的程序代码的线程去共享同一个资源。
③增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
联系: public class Thread implements Runnable(代理模式)
使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。
使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法:
public class NoNameInnerClassThread {
public static void main(String[] args) {
// new Runnable(){
// public void run(){
// for (int i = 0; i < 20; i++) {
// System.out.println("张宇:"+i);
// }
// }
// }; //---这个整体 相当于new MyRunnable()
Runnable r = new Runnable(){
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("张宇:"+i);
}
}
};
new Thread(r).start();
for (int i = 0; i < 20; i++) {
System.out.println("费玉清:"+i);
}
}
}