02-线程的两种创建方式继承Thread类和实现Runnable接口

2023-12-14 18:06:36

继承Thread类创建线程

获取线程对象

编写一个类继承Thread类并在重写的run方法中编写业务逻辑代码,那么这个类就是一个线程类

  • Runnable接口的run方法没有抛出任何异常,所以子类重写run方法时也不能抛出任何异常,对于程序执行中遇到异常时只能捕获不能抛出
// 重写方法抛出的异常范围不能大于被重写方法抛出的异常范围
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

需求: 在主线程中开启一个子线程,让这两个线程每隔一秒都在控制台交替输出,输出60次时结束主线程,输出80次时结束子线程

  • 一个Thread对象能且只能代表一个线程,如果一个Thread对象调用两次start()方法会抛出java.lang.illegalThreadStateException异常
public class Thread01 {
    // main方法是由主线程调用在主栈中运行
    public static void main(String[] args) throws InterruptedException {
        // 创建线程对象
        MyThread cat = new MyThread();
        // 此时表示在主线程中启动了一个子线程,最终会执行线程对象的run方法
        t.start();
		// 线程每隔1秒在控制台输出
        for(int i = 0; i < 60; i++) {
            System.out.println("主线程 i=" + i);
            //让主线程休眠
            Thread.sleep(1000);

        }
    }

// 定义一个线程类
class MyThread extends Thread {
    int times = 0;
    // 重写run方法写上自己的业务逻辑,run方法由MyThread线程调用并在分支栈中运行
    @Override
    public void run() {
        while (true) {
            // 线程每隔1秒在控制台输出
            System.out.println("子线程 times=" + (++times));
            // 让当前线程休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 当times到80退出当前线程
            if(times == 80) {
                break;
            }
        }
    }
}

run方法和start()方法的区别

在主线程中直接通过线程对象调用run方法并不会真正的启动一个线程,程序会先把run方法执行完后才会向下执行,因为此时程序还是只有一个主线程
在这里插入图片描述

通过线程对象调用start()方法会启动一个分支线程即在JVM中开辟一个新的栈空间,只要新的栈空间开出来start()方法就瞬间结束了此时线程也就启动成功了

  • 启动成功的线程会自动调用run方法即入口方法,并且run方法在分支栈的栈底部,执行一个线程最终就是执行该线程run()方法中的代码
  • run和main是平级的: run方法是子线程的入口方法在分支栈的栈底部, main方法是主线程的入口方法在主栈的栈底部

在这里插入图片描述

因为想抢占CPU的线程很多,只有抢到线程的CPU才会被执行,所以线程的可运行状态又分为就绪状态和运行状态

  • 线程的就绪状态: 线程具备了抢占CPU的资格但还没有被执行

  • 线程的运行状态: 线程抢占到了CPU可以被执行

通过线程对象调用start()方法后并不表示对应的线程就一定会立即得到执行,只是将线程状态变成了可运行状态中的就绪状态,底层还会调用start0()方法

在这里插入图片描述

public synchronized void start() {
    //....
    // 真正实现多线程的方法,由JVM调用,底层是c/c++实现,在这个实现的过程中调用了线程对象run方法
    start0();
}

// start0会调用线程对象的run方法
private native void start0();

实现Runnable接口创建线程

由于Java是单继承的,如果一个类已经继承了某个父类,就无法通过继承Thread类的方式创建线程,此时需要通过实现java.lang.Runnable接口并实现run方法

模拟线程代理类的功能实现

Runnable接口只有一个run方法并没有start方法,当我们执行线程对象的run方法时,是通过执行线程代理对象的run方法然后间接调用要目标线程对象的run方法

  • 代理模式: 线程代理类和目标线程类都实现了Runnable接口,线程代理对象一定内聚了要代理的目标线程对象

第一步: 创建需要代理的目标类且实现了Runnable接口

class Tiger extends Animal implements Runnable {
    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫....");
    }
}

第二步: 模拟Thread类创建一个线程代理类也实现了Runnable接口

public class ThreadProxy implements Runnable {
    // 内聚代理的目标对象,类型是Runnable类型
    private Runnable target = null;
    public ThreadProxy(Runnable target) {
         // 接收需要代理的目标对象
        this.target = target;
    }
    @Override
    public void run() {
        if (target != null) {
            // 动态绑定,对象的运行类型还是目标对象的类型
            target.run();
        }
    }
    public void start() {
        // 这个方法是真正实现多线程方法
        start0();
    }
    public void start0() {
        run();
    }
}

第三步: 获取目标对象的线程代理对象: 线程代理对象的start方法---->start0方法---->线程代理对象的run方法---->目标线程对象的run方法

public class Thread02 {
    public static void main(String[] args) {
        Tiger tiger = new Tiger();
        // 调用线程代理对象的start方法,最终调用目标线程对象的run方法
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}

获取线程代理对象

需求: 完成程序每隔一秒在控制台输出hi,当输出10次后自动退出

public class Thread02 {
    public static void main(String[] args) {
        // 创建一个可运行的普通对象
        MyRunnable r = new MyRunnable();
        // 为可运行的对象生成一个线程代理对象,含有start方法
        Thread thread = new Thread(r); 
        // 调用线程代理对象的start方法,最终调用目标线程对象的run方法
        thread.start();
    }
}
// 创建一个可运行的类还不是线程类
class MyRunnable implements Runnable { 
    int count = 0;
    @Override
    public void run() { //普通方法
        while (true) {
            System.out.println("hi" + (++count) + Thread.currentThread().getName());
            //休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}

采用匿名内部类的方式创建Runnable接口的实现类对象并为其创建代理对象

public class ThreadTest04 {
    public static void main(String[] args) {
        // 获取线程的代理对象
        Thread t = new Thread(new Runnable(){
            @Override
            public void run() {
                for(int i = 0; i < 100; i++){
                    System.out.println("t线程---> " + i);
                }
            }
        });
        // 启动线程
        t.start();
        for(int i = 0; i < 100; i++){
            System.out.println("main线程---> " + i);
        }
    }
}

在主线程中启动多个子线程一个Thread对象能且只能代表一个线程,如果一个Thread对象调用两次start()方法会抛出illegalThreadStateException异常

public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        thread1.start();//启动第1个线程
        thread2.start();//启动第2个线程
        //...
    }
}

class T1 implements Runnable {
    int count = 0;
    @Override
    public void run() {
        while (true) {
            // 每隔1秒输出 “hello,world”,输出10次
            System.out.println("hello,world " + (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count == 60) {
                break;
            }
        }
    }
}
class T2 implements Runnable {
    int count = 0;
    @Override
    public void run() {
        // 每隔1秒输出 “hi”,输出5次
        while (true) {
            System.out.println("hi " + (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count == 50) {
                break;
            }
        }
    }
}

实现Callable接口(8新特性)

获取线程对象

因为Runnable接口和Thread类中的run方法的返回值都是void,所以线程执行完任务之后是无法获取线程返回值的

如果系统委派的线程执行任务时会返回一个结果,如果想要拿到线程的执行结果需要实现Callable接口的call方法(有返回值)类似于run方法

  • 缺点: 在获取线程执行结果的时候,当前线程受阻塞降低了执行效率
方法名功能
public FutureTask(Callable接口实现类对象)创建一个可运行的未来任务类对象,指定线程执行方法的业务逻辑代码
Object get()获取线程的返回结果,执行该方法时会导致当前线程阻塞
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask; // JUC包下的java的并发包(新特性)
public class ThreadTest15 {
    public static void main(String[] args) throws Exception {
        // 创建一个可运行的未来任务类对象
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception { 
                // 线程执行一个任务,执行之后可能会有一个执行结果
                System.out.println("call method begin");
                Thread.sleep(1000 * 10);
                System.out.println("call method end!");
                int a = 100;
                int b = 200;
                // 自动装箱(300结果变成Integer)
                return a + b; 
            }
        });
        // 为task对象创建线程代理对象
        Thread t = new Thread(task);
        // 启动线程
        t.start();
        // 执行task对象的get方法获取t线程的返回结果,但会导致当前线程阻塞
        Object obj = task.get();
        System.out.println("线程执行结果:" + obj);
        // get方法是为了拿t线程的执行结果,所以需要等t线程执行结束后,主线程才能继续执行
        System.out.println("hello world!");
    }
}

继承类和实现接口的区别

区别与应用

通过继承Thread类或实现Runnable接口这两种方式来创建线程本质上没有区别,因为Thread类本身就实现了Runnable接口

继承Thread类的线程类最终执行的不是同一个线程对象的run方法,如果需要共享变量要将其声明为静态的

  • 执行流程: 调用Thread类的start方法---->调用Thread类的start0方法(启动线程对象)---->调用线程对象的run方法
SellTicket01 sellTicket01 = new SellTicket01();
SellTicket01 sellTicket02 = new SellTicket01();
SellTicket01 sellTicket03 = new SellTicket01();
//创建售票线程对象,最终调用线程类中run方法的对象是sellTicket01
sellTicket01.start();
//创建售票线程对象,最终调用线程类中run方法的对象是sellTicket02
sellTicket02.start();
//创建售票线程对象,最终调用线程类中run方法的对象是sellTicket03
sellTicket03.start();

实现Runnable接口的线程类最终可以执行同一个线程对象的run方法,如果需要共享对象不需要将其声明为静态的,因为就一个对象可以实现多个线程共享一个资源

  • 将线程对象传给Thread---->调用Thread类的start方法---->调用Thread类的start0方法(启动线程对象)---->调用Thread类的的run方法---->调用线程对象的run方法
SellTicket02 sellTicket02 = new SellTicket02();
// 创建不同的线程代理对象,最终调用线程类中run方法的对象都是sellTicket02
new Thread(sellTicket02).start();//第1个线程-窗口
new Thread(sellTicket02).start();//第2个线程-窗口
new Thread(sellTicket02).start();//第3个线程-窗口

文章来源:https://blog.csdn.net/qq_57005976/article/details/134998881
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。