1.Thread类的基本介绍

Thread类是Java标准库中提供用来管理线程的类,里面封装操作系统对线程管理方面的API,Java中创建的每一个线程,可以理解为Thread类的实例化对象,用来描述线程信息,可以视为Thread类是对操作系统提供线程管理的API进一步的抽象和分离。

2.创建线程

2.1 自定义线程类MyThread

定义一个MyThread类,继承于Thread类,Thread中有一个run方法,需要在子类中重写,MyThread类重写完成后,实例化MyThread的对象,就完成线程的创建,但是线程的真正的调用需要使用Thread类或者子类中的start方法,才会启动线程,在创建线程后就可以调用start方法

//自定义一个MyThread类继承于Thread类,出现Thread类中的方法
class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println("run方法已经启动。");
    }
}
public class Main{
    public static void main(String[] args){
        //实例化一个线程类
        MyThread myThread = new MyThread();
        //启动线程
        myThread.start();
    }
}

在这里插入图片描述

2.2 自定义一个MyRunable类

MyRunnable类使用Runnable接口,重写接口中的run方法,但是线程的创建需要使用到Thread类,因此还需要创建一个Thread类,将MyRunnable实例化对象作为Thread类构造方法的参数。

在Thread类中提供一个参数为Runnnable接口引用的构造方法,可以将引用传入。

//Thread类的构造方法
  public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("MyRunnable: run方法已经启动。");

    }
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t = new Thread(myRunnable);
    }
}

在这里插入图片描述

2.3 Thread 和 Runnable

Thread类是Java标准库提供用来管理线程的类,但是内部并没有具体实现run方法,而是调用Runnable接口中的run方法,Runnable是一个接口,内部也没有实现run方法,所以需要创建Thread的子类或者将使用Rnnbale接口的类的对象作为Thread构造方法的参数。

//Thread类的run方法
   @Override
    public void run() {
        if (target != null) {
            target.run();//进入run方法,实际使用Runnable的run方法
        }
    }

@FunctionalInterface
//Runnable的run方法
public interface Runnable {
    public abstract void run();
}

Thread的子类可以使用匿名内部类的方式创建,在子类中重写run方法。

//匿名内部类创建
class out{
    public static void main(String[] args) {
        //使用Thread作为匿名内部类的父类
        Thread thread = new Thread(){
            //重写run方法,实际调用的是重写的run
            @Override
            public void run(){
                System.out.println("out: run方法启动");
            }
        };

        //启动线程
        thread.start();
    }
}

在这里插入图片描述
Runnable的子类使用匿名内部类的方式创建,使用Runnable接收实例化对象,再将对象引用传入Thread的构造方法中。

class Rout{
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("Runnable: run方法启动");
            }
        };

        Thread thread = new Thread(runnable);

        //启动线程
        thread.start();
    }
}

在这里插入图片描述

2.4 lambda表达式

lambda表达式创建的匿名内部类的对象引用作为Thread类构造方法的参数传入。


class Lambda{
    public static void main(String[] args) {
        Thread thread = new Thread(
                //lambda表达式创建的
                () -> {System.out.println("Lambda: run方法已启动");}
        );

        //启动线程
        thread.start();
    }
}

在这里插入图片描述

3. sleep方法

sleep方法是让线程的执行进入等待,可以传入一个参数,单位是毫秒,例如sleep(1000)就是休眠等待一秒,sleep方法会让线程进入等待,这个方法默认是在线程中是不安全的,需要取捕获异常**InterruptedException**(每个让线程进入等待的方法都会捕获的异常),捕获异常的方法有两种,第一种是在sleep方法中加上try catch,第二种是在main方法中加上声明,最终由JVM处理异常。

例如 :重写的run方法中输出10次打印,每次间隔1000毫秒,记录调用sleep程序的执行的时间。

class Sleep{
    public static void main(String[] args) {
        
        Thread thread1 = new Thread(() ->{
           long start = System.currentTimeMillis();//开始
           
            for (int i = 0; i < 10; i++) {
                
                System.out.println("run方法打印中------");
                //捕获异常
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            };
            
           long end = System.currentTimeMillis();//接收
            System.out.println("时间:" + (end - start) + "ms");//毫秒
        });
        //启动线程
        thread1.start();
    }
}

在这里插入图片描述

4.currentThread方法

currentThread方法是Thread类中实现的静态方法,可以通过Thread类调用,currentThread方法可以获取当前线程执行的对象引用,使用该引用就可以执行Thread中的方法,如sleep,getName(获取线程名字)。

class Test{
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
        //记录sleep函数的执行时间
            long s = System.currentTimeMillis();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            long e = System.currentTimeMillis();

            System.out.println("sleep休眠时间:" + (e - s));
           //获取线程名
            System.out.println("获取当前线程名字:" + Thread.currentThread().getName());
        });

        thread.start();
    }
}

在这里插入图片描述
线程如果未命名,系统默认线程名是Thread-0,如果需要重命名可以在Thread的构造方法中多传入一个参数name,作为线程名字.

//Thread类中的构造方法,一个是Runnable对象引用,一个是线程名
   public Thread(Runnable target, String name) {
        this(null, target, name, 0);
    }
class Test{
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            long s = System.currentTimeMillis();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            long e = System.currentTimeMillis();

            System.out.println("sleep休眠时间:" + (e - s));

            System.out.println("获取当前线程名字:" + Thread.currentThread().getName());
        },"线程1");//添加线程名字

        thread.start();
    }
}

在这里插入图片描述

5.线程的终止

线程的终止有两种方法:

1.第一种是使用标识符,定义一个变量flag,通过改变flag使线程提前终止

例如:定义一个布尔类型的变量flag,初始默认为false,当flag更改为true时,停止线程。

class T{
    //定义标识符
    static boolean flag = false;
    public static void main(String[] args) {

        Thread thread = new Thread(() -> {
        //标识符控制
            while(!flag){
                System.out.println("线程最执行中----");
                System.out.println("输入1停止线程,输入其它继续线程");
                System.out.print("请输入:");
                Scanner in = new Scanner(System.in);
                if(in.nextInt() == 1) flag = true;//改变标识符终止线程
            }
            System.out.println("线程结束");
        });

        thread.start();
    }
}

在这里插入图片描述

如果此时将标识符设置在mian函数内,是否可以正确执行呢?

在这里插入图片描述
可以看到将标识符在main方法定义,程序就会报错,错误是因为lambda表达式内部变量的使用会触发“变量捕获” 的机制,被捕获的局部变量需要满足是被final修饰的,或者实际上是final的(不被修改的),当flag是可被修改的就会在编译时报错。

多个线程的执行是并发执行的,例如在上述出现中就有main线程(主线程)和Thread-0线程,在Thread线程开启的同时,main线程也会开启,并发执行,在lambda中使用的变量是一份临时的拷贝,并不是原有的变量,main线程结束时,捕获的变量也可以继续使用,不会因线程的结束而无法使用,但是如果捕获的变量是可修改的,main线程可能修改后,还未等到lambda表达式去拷贝修改后的变量就结束线程,导致捕获的结果错误,所以lambda表达式中的局部变量必须是final或者实际上是final的。

在这里插入图片描述

为什么在类中定义的静态变量不会报错?

类中的静态变量在类加载的时候存放于静态区,访问可以通过类或者成员方法访问,类成员变量的销毁伴随类的销毁,而线程的结束通常是比类的销毁更快(类的销毁涉及GC垃圾回收的复杂机制),所以在类中定义的成员变量在lambda表达式中不会出现报错信息。

2.线程终止的第二种方法是调用interrupt方法,提前终止线程,interrupt方法通常搭配isInterruoted方法使用,isInterrupted方法可以认位是调用标识符,interrupt方法是将标识符改变,终止线程。

//isInterrupted方法默认调用标识符,interrupted = false
 private volatile boolean interrupted;
class Interrupt{
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            //判断是否终止线程
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("线程执行中.....");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                  //未输出异常原因
                }
            }
        });

        //开启线程
        thread.start();
        //确保调用interrupt方法线程开始执行
        Thread.sleep(1);
        //终止线程
       thread.interrupt();
    }
}

在这里插入图片描述
程序执行后会一直打印线程执行中,并没有终止线程,这是因为在调用interrupt方法的时候会先把isInterrupted方法获取的标识符改为true,但是interrupt方法会重新唤醒sleep方法,又将标识符改为false,导致线程继续执行。

可以在捕获异常后执行输出异常的原因,终止线程。

     try {
            Thread.sleep(1000);
    } catch (InterruptedException e) {
            throw new RuntimeException(e);//抛出异常,终止进程
    }

在这里插入图片描述
在catch中直接加入break,可以终止线程,不会立马终止进程。

   try {
           Thread.sleep(1000);
       } catch (InterruptedException e) {
           System.out.println("线程结束");
                break;
      }

在这里插入图片描述

6.小结

本篇内容涵盖了Java EE中Thread类的基本使用,包括线程创建、休眠、获取当前线程以及线程终止等核心操作。掌握这些基础知识为理解多线程编程的状态管理、线程安全及设计模式应用奠定基础。后续内容将深入探讨线程的状态转换及相关技术细节,进一步扩展Java EE多线程编程的实践能力。

期待下次继续带你深入Java EE的更多技术细节,下期见!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐