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进行投诉反馈,一经查实,立即删除!