02-线程的两种创建方式继承Thread类和实现Runnable接口
继承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个线程-窗口
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!