理解Java Thread类的基本用法

一、认识线程-Thread类

一个线程就是一个“执行流”。每个线程都可以按照顺序执行自己的代码,而多个线程可以 "同时" 执行多份代码。

就相当于:

  ⼀家公司要去银⾏办理业务,既要进⾏财务转账,⼜要进⾏福利发放,还得进⾏缴社保。 如果只有张三⼀个会计就会忙不过来,耗费的时间特别⻓。为了让业务更快的办理好,张三⼜找来两 位同事李四、王五⼀起来帮助他,三个⼈分别负责⼀个事情,分别申请⼀个号码进⾏排队,⾃此就有 了三个执⾏流共同完成任务,但本质上他们都是为了办理⼀家公司的业务。 此时,我们就把这种情况称为多线程,将⼀个⼤任务分解成不同⼩任务,交给不同执⾏流就分别排队 执⾏。其中李四、王五都是张三叫来的,所以张三⼀般被称为主线程(Main Thread)。

  线程本身是操作系统中的概念。操作系统内核实现了线程这样的机制,并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库)。而Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装。

  Thread 类是 JVM 用来管理线程的一个类。在Java中,每个线程执行流都是通过Thread类的对象来描述的。JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

1、Thread类的常见构造方法

1.hread t1 = new Thread();

2.Thread t2 = new Thread(new MyRunnable());

3.Thread t3 = new Thread("这是我的名字");

4.Thread t4 = new Thread(new MyRunnable(), "这是我的名字"); 

2、Thread 的几个常见属性

• ID 是线程的唯⼀标识,不同线程不会重复

• 名称是各种调试⼯具⽤到

• 状态表⽰线程当前所处的⼀个情况,下⾯我们会进⼀步说明

• 优先级⾼的线程理论上来说更容易被调度到

• 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。

• 是否存活,即简单的理解,为 run ⽅法是否运⾏结束了

• 线程的中断问题,下⾯我们进⼀步说明

二、Thread类的基本用法

1、创建线程

方法一  继承 Thread 类

1.继承 Thread 来创建⼀个线程类,在 MyThread类 中重写 run() 入口方法

class MyThread extends Thread {
 @Override
 public void run() {
 System.out.println("这⾥是线程运⾏的代码");
 }
}

2.创建 MyThread 类的实例

MyThread t = new MyThread();
3.调⽤ start ⽅法启动线程

t.start(); // 线程开始运⾏

运行结果

方法二  实现 Runnable 接口

1. 实现 Runnable 接⼝

class MyRunnable implements Runnable {
 @Override
 public void run() {
 System.out.println("这⾥是线程运⾏的代码");
 }
}
2. 创建 Thread 类实例, 调⽤ Thread 的构造⽅法时将 Runnable 对象作为 target 参数.

Thread t = new Thread(new MyRunnable());

调⽤ start ⽅法
t.start(); // 线程开始运行
  对⽐上⾯两种⽅法:  继承 Thread 类, 直接使⽤ this 就表⽰当前线程对象的引⽤.
实现 Runnable 接⼝, this 表⽰的是 MyRunnable 的引⽤. 需要使⽤ Thread.currentThread()

在Java中,有两种常见的创建线程的方式:继承Thread类和实现Runnable接口。

  1. 继承Thread类: 当你继承Thread类创建线程时,this关键字代表当前线程对象的引用。通过this可以直接访问当前线程对象的方法和属性。

public class MyThread extends Thread {
    public void run() {
        // 在这里编写线程执行的代码
        System.out.println("This is a thread extending Thread class");
    }
}

MyThread myThread = new MyThread();
myThread.start();
 

  1. 实现Runnable接口: 当你实现Runnable接口创建线程时,this关键字代表实现了Runnable接口的类的对象引用。因为Runnable接口不是线程本身,而是一个任务,需要将其传递给Thread对象来创建线程。

public class MyRunnable implements Runnable {
    public void run() {
        // 在这里编写线程执行的代码
        System.out.println("This is a thread implementing Runnable interface");
    }
}

MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
 

Thread.currentThread()是一个静态方法,它返回当前正在执行的线程对象的引用。这个方法可以在任何地方调用,无论是在继承Thread类还是实现Runnable接口的类中。Thread.currentThread()方法的返回值是一个Thread对象,代表当前执行的线程。

匿名内部类创建 Thread 子类对象

1.匿名内部类创建 Thread ⼦类对象

// 使⽤匿名类创建 Thread ⼦类对象
Thread t1 = new Thread() {
 @Override
 public void run() {
 System.out.println("使⽤匿名类创建 Thread ⼦类对象");
 }
};

2.匿名内部类创建 Runnable ⼦类对象

// 使⽤匿名类创建 Runnable ⼦类对象
Thread t2 = new Thread(new Runnable() {
 @Override
 public void run() {
 System.out.println("使⽤匿名类创建 Runnable ⼦类对象");
 }
});
        匿名内部类创建 Runnable 子类对象(最常用使用)
// 使⽤ lambda 表达式创建 Runnable ⼦类对象
Thread t3 = new Thread(() -> System.out.println("使⽤匿名类创建 Thread ⼦类对象"));
Thread t4 = new Thread(() -> {
 System.out.println("使⽤匿名类创建 Thread ⼦类对象");
});

2、启动线程        start()

(1)start()方法

start()方法是启动线程的方法,调用该方法会使线程进入就绪状态,等待CPU分配时间片后开始执行。run()方法是线程的执行体,它包含了线程要执行的代码;当调用start()方法启动线程后,线程会在独立的执行路径上自动执行run()方法中的代码(也就是说,当调用start()启动线程后,系统会自动调用run()方法执行线程的执行体)。

但要注意的是,run()方法并不标识新的线程的创建;调用 start 方法,才真的在操作系统的底层创建出了一个线程。

start()方法的使用:在创建完线程后,通过线程的实例调用start()即可。

(2)start()方法与run()方法的区别

在Thread类中,run()方法是线程的执行体,包含了线程要执行的具体任务代码。而start()方法是启动线程的方法,调用start()方法会创建一个新的线程并执行run()方法中的代码。

从方法的区别来看:

  • run()方法是Thread类中的一个普通方法,可以被直接调用,但不会创建新的线程来执行其中的代码。
  • start()方法是Thread类中的一个特殊方法,调用start()方法会创建一个新的线程,并在新线程中执行run()方法中的代码。

从运行结果的区别来看:

  • 如果直接调用run()方法,代码会在当前线程中顺序执行,不会创建新的线程。
  • 如果调用start()方法,会创建一个新的线程,新线程会并发执行run()方法中的代码,实现多线程并发执行的效果。
(3)start()方法与run()方法的区别--总结

简单来说,直接调用run()方法不会创建新线程,代码在当前线程中执行;而调用start()方法会创建新线程,并在新线程中执行代码,实现多线程并发执行的效果。

3、线程中断        interrupt()

线程的中断就是字面意思:让一个线程停下来。也即线程的终止。

本质上来说,让一个线程终止的唯一方法是让该线程的入口方法run()执行完毕。

(1)给线程中设定一个结束标志位 isQuit

可以给线程设置一个结束标志位isQuit,以便在适当的时候结束线程的执行。

(2)注意 isQuit 的书写位置

isQuit的书写位置应该是在线程的run()方法中进行检查,以决定是否终止线程的执行。

(3)使用Thread类内置的标志位 isInterrupted()

可以使用Thread类内置的标志位isInterrupted()来检查线程是否被中断。

(4)interrupt() 方法的作用

调用interrupt()方法可以设置线程的中断状态,但实际上并不会立即中断线程的执行,而是设置一个中断标志位,由线程自行决定如何响应中断。

(5)为什么sleep()要清空标志位呢?

在使用sleep()方法时,通常会在捕获InterruptedException异常后清空中断标志位,这是因为sleep()方法在捕获到中断信号时会抛出InterruptedException异常,并且会清除中断状态,如果不清除中断状态,线程将无法正确响应后续的中断信号。

以下是一个简单的Java代码示例,演示了如何在线程中使用中断标志位isQuitisInterrupted()方法来实现线程中断:

public class MyThread extends Thread {
    private volatile boolean isQuit = false;

    @Override
    public void run() {
        while (!isQuit) {
            System.out.println("Thread is running...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Thread is interrupted!");
                // 清空中断状态
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("Thread is stopped.");
    }

    public void stopThread() {
        isQuit = true;
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();

        // 模拟在一定时间后中断线程
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread.stopThread(); // 设置结束标志位
        thread.interrupt(); // 中断线程
    }
}

在这个例子中,线程会每隔1秒输出一次"Thread is running...",在5秒后会调用stopThread()方法设置结束标志位isQuit为true,同时调用interrupt()方法中断线程。在run()方法中,通过检查isQuit标志位和捕获InterruptedException异常来实现线程的中断。

4、线程等待        join()

在Java中,线程的join()方法也可以用来等待线程执行完毕。当调用一个线程的join()方法时,当前线程将会被阻塞,直到被调用的线程执行完毕。

(1)join()方法,无参数

join()方法没有参数时,当前线程会一直等待被调用的线程执行完毕。

(2)join()方法,带参数

join(long millis)方法带有参数时,可以设置一个超时时间(单位为毫秒),如果被调用的线程在超时时间内没有执行完毕,当前线程将会继续执行。具体语法如下:

thread.join(timeout);
 

其中,timeout是等待的最大时间,如果超过这个时间被调用的线程仍未执行完毕,当前线程将不再等待,继续执行后续代码。

public class JoinExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            System.out.println("Thread 1 is running");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread 1 is finished");
        });

        Thread thread2 = new Thread(() -> {
            System.out.println("Thread 2 is running");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread 2 is finished");
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join(); // 等待thread1执行完毕
            thread2.join(1500); // 等待最多1.5秒,如果超时则不再等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main thread is finished");
    }
}

在上面的示例中,主线程启动了两个子线程thread1thread2,然后分别调用了join()方法来等待这两个线程执行完毕。可以自己尝试一下。

5、线程休眠        sleep()

sleep()线程休眠的方法前面已经使用过。需要注意的有两点,一是该方法是Thread类的一个静态方法,由Thread类名直接调用:Thread.sleep();二是该方法存在一个受查异常,在使用这个方法时,需要try-catch或throw来处理这个异常。还有⼀点要记得,因为线程的调度是不可控的,所以,这个⽅法只能保证 实际休眠时间是⼤于等于参数设置的休眠时间的。

6、获取线程实例        currentThread()

能获取当前线程对象的实例。在哪个线程里调用,得到的就是哪个线程对象的实例。需要的注意的是,该方法是Thread类的静态方法,由Thread类直接调用。

public class demo7 {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();    //获取当前对象实例
        System.out.println(thread.getName());
    }
}
  • 15
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
目 录 第一章 JAVA入门 10 计算机语言发展史 10 机器语言 10 汇编语言 10 高级语言 10 其他高级语言 11 JAVA发展简史 12 JAVA为什么能够流行? 13 JAVA各版本的含义 13 JAVA技术体系架构 14 JAVA的特性和优势 14 JAVA应用程序的运行机制 15 JVM(JAVA VIRTUAL MACHINE) 16 Java运行时环境JRE(Java Runtime Environment) 17 JAVA语言应用范围 18 第一个JAVA程序 18 JAVA开发环境搭建 18 一个典型的JAVA程序的编写和运行过程 19 第一个程序常见错误 20 第一个JAVA程序的总结和提升 20 常用Java开发工具 20 常用dos命令 21 本章笔试作业 21 本章上机操作 21 第二章(1) 编程的基本概念 22 注释 22 标识符 22 关键字/保留字 23 变量(variable) 24 常量(Constant) 25 命名规则(规范) 25 基本数据型(primitive data type) 26 整型变量 26 浮点型 27 字符型(2个字节): 28 boolean型 29 运算符(operator) 29 二元运算符 29 一元运算符 30 布尔逻辑表达符 30 位运算符 30 扩展运算符 31 字符串连接符 31 三目条件运算符 31 运算符优先级的问题 31 自动型转换 32 基本型转化时常见错误和问题 33 方法 33 简单的键盘输入和输出 33 本章思考作业 34 上机操作 34 第二章(2) 控制语句 35 顺序结构 35 选择结构 35 if单选择结构 35 if-else双选择结构 35 If-elseif-else多选择结构 36 switch多选择结构 37 循环结构 39 While和dowhile的区别 41 For循环 42 break语句和continue语句 47 语句块 48 递归结构 49 本章作业 50 本章上机操作 51 第三章 JAVA面向对象程序开发 52 编程语言发展史 52 和对象是如何产生发展的?如何进化的? 52 面向对象思想初步(OOP初步Object Oriented Programming) 53 面向对象编程的语言的三大特征简介 56 对象和的概念 56 和对象初步 57 测试的定义方式 57 简单的学生编写示例 58 内存分析 59 属性(field,或者叫成员变量) 59 引用型 60 的方法 60 对象的创建和使用 60 构造器(或者叫做构造方法,constructor) 60 垃圾回收机制(Garbage Collection) 63 方法的重载(overload),构造方法的重载 63 this关键字 65 static 关键字 66 静态初始化块(经常用来初始化,加载信息时执行!) 67 package 68 JDK中的主要包 68 import 68 eclipse的使用 69 继承(extend, inheritance) 70 为什么需要继承?继承的作用? 70 继承介绍 70 如何实现继承? 70 继承使用要点 71 Object 72 toString方法 72 equals方法 73 super关键字 74 方法的重写(override) 74 隐藏/封装(encapsulation) 75 为什么需要封装?封装的作用和含义? 75 使用访问控制符,实现封装 76 封装的使用细节 76 多态(polymorphism) 76 为什么需要多态? 76 如何实现多态? 77 方法绑定(method binding) 77 静态绑定 77 动态绑定 77 多态的使用要点 78 对象的转型(casting) 79 final 81 抽象 82 抽象的使用要点 83 接口 83 为什么需要接口? 84 如何定义接口? 84 接口的本质探讨 84 接口使用要点 85 接口的多继承 86 面向接口编程 87 OOP更多应用 87 组合 87 内部(innerclasses) 88 字符串(java.lang.String)的使用 90 字符串相等的判断 92 思考作业 93 上机作业 94 第四章 异常机制 95 导引问题 95 异常(Exception)的概念 96 异常分 96 Error 97 Error和Exception的区别 97 Exception 97 异常的处理办法之一,捕获异常 99 try块 99 catch 99 finally 100 try, catch,finally ,return 执行顺序 100 异常的处理办法之二,声明异常:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值