理解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
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值