线程的创建、启动、运行与终止

本文详细介绍了Java中的线程概念,包括Thread类的属性、构造方法、运行机制、线程间的协作(如join和sleep)、优先级以及线程的中断和终止。重点讲解了如何创建线程、控制线程行为和正确处理线程间通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        Java的线程和操作系统线程的关系

        线程是操作系统中的概念.操作系统内核实现了线程这样的机制,并且对用户层提供了一些使用.

        Java标准库中的Thread类可以视为是对操作系统提供的API进行了进一步的抽象和封装.

        Thread类

        在Java中,与线程创建、启动有关的是一个类,叫做Thread。接下来就通过介绍这个类中的一些方法来了解Java中的线程

        Thread类的几个常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
是否后台线程isDaemon()
是否存活isAilve()
是否被中断isInterrupted()
优先级       getPriority()

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

        名称是各种调试⼯具⽤到

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

        关于后台线程,只需要牢记:JVM只有在所有非后台线程结束后,才会结束运行

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

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

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

        Thread类的构造方法 || 线程的创建

        

方法说明
Thread()创建线程对象
Thread(Runnable target)使用Runnable对象创建线程对象
Thread(String name)创建线程对象并重命名为name
Thread(Runnable target,String name)使用Runnable对象创建线程对象,并重命名为name

                了解了这四种构造方法后,创建线程对象主要分为两大种:继承Thread类和实现Runnable接口

                run()的用法在下文会解释

                1)继承Thread类
class MyThread extends Thread{
    //重写run方法
    @Override
    public void run() {
        System.out.println("这是一个线程");
    }
}
public class Text01 {
    public static void main(String[] args) {
        //创建线程对象
        Thread t = new MyThread();
    }
}
                2)实现runnable接口
class MyThread implements Runnable{
    //重写run方法
    @Override
    public void run() {
        System.out.println("这是一个线程");
    }
}
public class Text01 {
    public static void main(String[] args) {
        //创建 Runnable 接口的对象
        Runnable target = new MyThread();
        //通过Runnable接口创建线程对象
        Thread t = new Thread(target);
    }
}
        3) 继承Thread类,但是通过匿名内部类的方式实现
public class Text01 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("这是一个线程");
            }
        };
    }
}
        4)实现Runnable接口,但是通过匿名内部类的方式实现
public class Text01 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("这是一个线程");
            }
        });
    }
}
        5)通过lamabda表达式创建

        这种方式是对 实现Runnable接口,但是通过匿名内部类的方式实现 的一种简写

public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("这是一个线程");
        });
    }

        线程的运行

        线程的运行是通过Thread类对象的 start()方法开始的,调用start方法后,jvm就会自动帮我们执行run方法,开启这个线程,可以理解为 run方法中的内容就是这个线程要执行的工作

public class Text01 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("这是一个线程");
        });
        t.start();
    }
}

        在运行这段代码后,命令台就会输出 这是一个线程

        值得注意的是,线程之间是并行的,

举个例子(sleep方法是让线程休眠指定时间,单位ms):

public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while(true) {
                System.out.println("这是一号线程");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread t2 = new Thread(() -> {
            while(true) {
                System.out.println("这是二号线程");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        t2.start();
    }

这段代码的结果为:

       代码中是两个死循环,如是按照以前的串行方式,必然只会打印一个,但是因为线程是并行的,所以可以同时运行.

        等待一个线程

        在实际应用中,有时我们需要等待一个线程的结果才能继续执行下面的代码

        比如:计算1-10000的和,可以设计两个线程分别计算1-5000的和 和 5001-10000的和

        那么在main中创建并执行这两个线程之后,就需要等待两个线程计算完毕才能输出计算的结果,如果不进行等待 那么输出的结果很大概率是错误的。

        因此 Thread类就提供了一个方法 :join(),调用这个方法后 会等待调用这个方法的线程对象执行完毕才会执行join 下面的代码,举个例子:

public class Text01 {
    public static int sum1 = 0;
    public static int sum2 = 0;
    public static void main(String[] args) throws InterruptedException {
        //创建两个线程 分别计算1-5000的和 和 5001-10000的和
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                sum1 += i;
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 5001; i < 10000; i++) {
                sum2 += i;
            }
        });
        //启动两个线程
        t1.start();
        t2.start();
        //等待两个线程执行完毕
        t1.join();
        t2.join();
        //输出结果
        System.out.println("sum:" + (sum1 + sum2));
    }
}

        有两点需要注意

        1.join方法可能会抛出一个 InterruptedException 异常,调用join方法的时候需要对这个异常进行处理(try catch 或者 再次抛出给方法的调用者)

        2.join的位置不可以随便( 在上面的代码中start t1、t2后 使用了t1.join t2.join 意为:启动t1线程、启动t2线程、等待t1线程执行结束、等待t2线程执行结束)

        join()方法在使用时还可以传入参数

        

方法说明
join()等待线程结束
join(long millis)等待线程结束,最多等待millis毫秒
join(long millis,int nanos)同上,但是更精确

        获取当前线程的引用

        获取当前线程的引用使用currentThread()方法

        有时我们在创建线程时,需要调用线程的其他方法,这时就需要使用currentThread()获取当前线程的引用后才能调用其他方法

        假设在线程执行的开始 我要打印该线程的名字:

 public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(t1.getName());
            //这里报错时因为 t1还未创建 所以无法识别t1是什么
        });
        Thread t2 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName());
            //在这里调用currentThread()获得当前线程的引用 即t2,后调用getName输出名字
        });
        //启动两个线程
        t1.start();
        t2.start();
    }

        休眠线程

        休眠即让该线程不去cpu上工作,有⼀点要记得,因为线程的调度是不可控的,所以,这个⽅法只能保证 实际休眠时间是⼤于等于参数设置的休眠时间的。

方法说明
sleep(long millis)休眠millis毫秒
sleep(long millis,int nanos)同上,但是更精确

        要注意:sleep方法会抛出InterruptedException 异常。

        什么时候会抛出这个异常? 在sleep被异常唤醒时

        如果线程因为调⽤ sleep⽅法⽽阻塞挂起,当sleep被异常唤醒,它就会抛出InterruptedException,此时就会清除线程的中断标志

        中断一个线程

        在线程的应用中,有时会发生变数导致我们需要停止一个正在运行中的线程。

        线程终止有三种方法

        1)线程自然终止,即运行完毕run方法中的内容

        2)设置标识符,通知该线程应该终止

        3)使用stop方法,这个方法不推荐使用,因为是极其不安全的!

        上述三种(实际两种)方法,如果我们希望不执行完毕这个线程,那么只能使用第二种:设置标识符

        创建一个boolean对象isQuit,修改这isQuit的值来控制线程是否继续执行(不推荐)

public class Text01 {
    public static boolean isQuit = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!isQuit){
                System.out.println("running");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        //main中休眠5s让t工作
        Thread.sleep(5000);
        //将isQuit设置为true来中断线程
        isQuit = true;
    }
}

        

观察运行结果可以发现,t线程运行5s之后终止了

        终止的另一种发放就是调用线程的属性 “是否被中断” 来判断

public class Text01 {

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("running");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        //main中休眠5s让t工作
        Thread.sleep(5000);
        //将t的属性 "是否被中断" 设置为true
        t.interrupt();
    }
}

观察运行结果发现,t线程运行一段时间后 抛出了一个异常 "InterruptedException"(很眼熟对不对),但是并没有停止运行

思考一下为什么会抛出异常?

在t的运行中 t的运行逻辑为 打印一次running 之后sleep上5s,打印一次的耗时可以说非常小

所以当我们随机访问t的”时间轴“时,t很大概率在sleep.

在main中 我们设置t的属性“是否被中断”为true,此时就会访问t,但是此时t在sleep

那么,t就会被异常唤醒,在上文的"休眠线程"中说过:如果线程因为调⽤ sleep⽅法⽽阻塞挂起,当sleep被异常唤醒,它就会抛出InterruptedException,此时就会清除线程的中断标志

中断标志被清除后,while就会判断 !Thread.currentThread.isInterrupted 为true(因为Thread.currentThread.isInterrupted 被重置为了false)所以循环并没有被打破,t线程继续执行

思考 在这种情况下如何终止呢?

因为抛出异常后我们可以在catch中做额外的语句,所以我们可以在catch中再次设置“是否被中断”为true,这样就可以正常的终止这个线程了

public class Text01 {

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("running");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //这里将打印异常信息删除仅仅为了运行结果好看
                    Thread.currentThread().interrupt();
                }
            }
        });
        t.start();
        //main中休眠5s让t工作
        Thread.sleep(5000);
        //将t的属性 "是否被中断" 设置为true
        t.interrupt();
    }
}

这次可以观察到,t线程执行5s之后正常终止

         以上两种方法介绍完毕后,我们发现 要去终止这个线程必须需要线程内部进行”配合“

        如上面的判断 !Thread.currentThread.isInterrupted

        如果线程不主动配合我们进行终止线程,那我们就无法正常的终止这个线程(这是由Java语法所规定的)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值