认识Thread类

认识Thread类

Thread类是 JVM用来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的Thread对象与之关联

Thread类提供了创建和管理线程的方法和功能,通过使用Thread类,可以同时执行多个任务,实现并行处理。

Thread常见的构造方法

Thread()创建Thread对象
Thread(Runnable target)使用Runnable对象创建Thread对象
Thread(Runnable target, String name)使用Runnable对象创建Thread对象,并命名
Thread(String name)创建Thread对象,并进行命名

Thread类中几个常见的静态方法(通过类名调用)

返回类型方法名说明
ThreadcurrentThread()获取当前线程对象的引用
Threadinterrupted()检查当前线程是否已经被中断
voidsleep(long millis)使当前正在执行的线程休眠(单位毫秒)
voiddumpStack()将当前线程的堆栈跟踪信息输出到标准错误流
voidyield()提示调度器当前线程愿意放弃当前的CPU时间片

Thread类中几个常见的实例方法(通过实例化对象使用)

其中标红的为Thread的属性

返回类型方法名说明
longgetId()获得ID
StringgetName()获取线程名称
intgetPriority()优先级
StackTraceElement[]getStackTrace()堆栈跟踪信息
Thread.State getState()线程状态
voidinterrupt()中断对象关联的线程
booleanisAlive()是否存活
booleanisDaemon()是否为后台线程
booleanisInterrupted()判断当前线程的中断标志位是否设置
voidsetDaemon(boolean on)设置为后台线程
voidsetName(String name)设置线程名字
voidrun()覆写run方法
voidstart()启动线程
voidjoin()等待线程
voidsleep(long millis)休眠当前线程


• ID是线程的唯⼀标识,不同线程不会重复,这里的id是java给的id
• 名称在各种调试工具用到,通过jconsole.exe可查看
• 状态表示线程当前所处的⼀个情况
• 优先级高的线程理论上来说更容易被调度到
• 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有非后台线程结束后,才会结束运行,而后台线程不影响java进程的结束。
• 是否存活,简单的理解为run方法是否运行结束了

方法的使用

public class Demo12 {
    public static void main(String[] args) {
        Thread t=new Thread(()->{
            for (int i = 0; i < 5; i++){
                System.out.println(Thread.currentThread().getName()+"我还没结束");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println(Thread.currentThread().getName()+"我即将结束");
        });
        System.out.println(Thread.currentThread().getName()+":创建的线程ID:"+t.getId());
        System.out.println(Thread.currentThread().getName()+":创建的线程名称:"+t.getName());
        System.out.println(Thread.currentThread().getName()+":创建的线程状态:"+t.getState());
        System.out.println(Thread.currentThread().getName()+":创建的线程优先级:"+t.getPriority());
        System.out.println(Thread.currentThread().getName()+":创建的线程后台线程:"+t.isDaemon());
        System.out.println(Thread.currentThread().getName()+":创建的线程存活:"+t.isAlive());
        System.out.println(Thread.currentThread().getName()+":创建的线程被中断:"+t.isInterrupted());
        t.start();
        while (t.isAlive()) {}
        System.out.println(Thread.currentThread().getName()
                + ": 状态: " + t.getState());
    }
}

创建线程

认识了基本的方法之后我们就要开始创建第一个线程了,一定要通过线程变量名.start()去创建线程。start()方法是Java提供的API来调用系统中创建线程的方法,run()方法是线程要干的事,在线程创建好之后会自动调用

实现类继承Thread类

通过创建一个新的类继承Thread类,并重写其run方法,然后在创建该类的实例并调用start方法来启动线程。

我们在覆写run方法定义线程进行的操作是写一个循环,但是为了便于观察,我们让线程休眠(阻塞状态),这时候会报异常(受检查异常
在这里插入图片描述
try-catch处理异常
在main方法里使用Thread.sleep同样需要处理异常,此时抛一个InterruptedException异常即可

public class Demo1 {
    static class MyThread extends Thread{
        //继承父类需要重写父类中的run方法
        @Override
        public void run() {
           //执行操作 
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t=new MyThread();//向上转型
        t.start();//真正创建了进程
        }
    }
}

实现Runnable接口

通过创建一个新的类实现Runnable接口,并重写其run方法,然后将该类的实例作为target参数传递给Thread类的构造函数,最后调用Thread对象的start方法来启动线程。

public class Demo2 {
    static class Myrunnable implements Runnable{
        @Override
        public void run() {
        //执行操作
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable=new Myrunnable();
        Thread t=new Thread(runnable);//带参数的构造方法,也做到了解耦合
        t.start();//使得该线程开始执行;Java 虚拟机调用该线程的 run 方法
    }
}

使用匿名内部类

无论是继承Thread类还是实现Runnable接口,都可以使用匿名内部类的方式来创建线程,这种方式更加简洁。

public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        //匿名内部类,创建了Thread的子类,创建了实例,并赋值给t这个对象,在{}里可以定义子类的方法,属性,重写父类的方法
        //如果某些代码是一次性的,不需要重复使用的,就可以使用匿名内部类
        Thread t = new Thread() {
            @Override
            public void run() {
				//执行操作
            }
        };
        t.start();//使得该线程开始执行;Java 虚拟机调用该线程的 run 方法。
    }
}
public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
               //执行操作
            }
        };
        //使⽤匿名类创建 Runnable ⼦类对象
        Thread t=new Thread(runnable);//这种方式方便以后修改代码,降低耦合,可以通过其他方式去完成线程,比如线程池,协程,到时候只需要改变传入的参数即可
        t.start();//使得该线程开始执行;Java 虚拟机调用该线程的 run 方法。
    }
}

使用lambda表达式(推荐)

    lambda——本质上是匿名函数,主要用途作为“回调函数”
    ()->{}为函数式接口
    创建了一个匿名的函数式接口的子类,并创建了对应的实例,并重写了里面的方法

举例:交替打印 “hello Thread” 和 “hello main”

public class Demo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t.start();//使得该线程开始执行;Java 虚拟机调用该线程的 run 方法。
        while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

打印的结果(是个死循环)
在这里插入图片描述
多运行几次可以发现hello Thread和Hello main的打印顺序并没有一致,这是由于系统调度器随机调度导致的

使用Callable创建线程

Callable和Runnable是并行关系
创建Callable对象,重写call()方法,不同于Runnable的是,Callable需要通过FutureTask/Future把Callable任务包装成一个FutureTask对象

获取结果通过get()方法,如果任务未完成,get方法会阻塞

public class Demo29 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable=new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int result=0;
                for (int i = 1; i <= 1000; i++) {
                    result+=i;
                }
                return result;
            }
        };
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t=new Thread(futureTask);
        t.start();
        System.out.println(futureTask.get());
    }
}

通过线程池创建线程

Java 提供了 java.util.concurrent 包中的 ExecutorService 接口来管理线程池。

Executors.newCachedThreadPool()是Executors类中的一个静态方法,它创建了一个线程池,这个线程池会根据需要创建新线程,但是当现有线程空闲(60秒)时会被终止并移除。这意味着,这个线程池在需要时能够迅速响应并执行任务,而在任务完成后不会保留大量空闲线程,从而节省了系统资源。

需要通过submit()提交任务给线程池

public class Demo33 {
    public static void main(String[] args) {
        ExecutorService pool= Executors.newCachedThreadPool();
        for(int i=0;i<10;i++){
            int id=i;
            pool.submit(()->{//提交任务
                System.out.println("任务 "+id);
            });
        }
        pool.shutdown();//关闭线程池

    }
}

中断线程

interrupt():中断本线程(将中断状态标记为true)

isInterrupted():判断当前线程的中断标志位是否设置,调用后清除标志位,不建议使用,静态方法为所有线程共用的
interrupted():判断对象关联的线程的标志位是否设置,调用后不清除标志位

举例:main线程中断t线程

public class Demo10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while(!Thread.currentThread().isInterrupted()){//未被中断
                System.out.println("Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                   //break;立即终止
                    //先执行一些其他逻辑的代码再break,稍后终止
                    //什么都不写 不终止
                    break;
                }
            }
            System.out.println("t:结束");
        });
        t.start();
        Thread.sleep(3000);
        System.out.println("main线程尝试终止t线程");
        t.interrupt();//线程结束,会唤醒休眠的sleep(阻塞),sleep会把isInterrupted设置成true
    }
}

thread收到通知的方式有两种:

  1. 如果线程因为调用wait/join/sleep等方法而阻塞挂起,则以InterruptedException异常的形式通知,清除中断标志。当出现InterruptedException的时候,要不要结束线程取决于catch中代码的写法.可以选择忽略这个异常,也可以跳出循环结束线程.

  2. 只是内部的⼀个中断标志被设置,thread可以通过 Thread.currentThread().isInterrupted()判断指定线程的中断标志被设置,不清除中断标志
    这种方式通知收到的更及时,即使线程正在sleep也可以马上收到。

线程等待

有时,我们需要等待⼀个线程完成它的工作后,才能进行自己的下⼀步工作。

举例:main线程等待t线程结束

public class Demo11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    break;
                }
            }
            System.out.println("t线程结束" );
        });
        t.start();
        t.join();//main线程等待t线程结束,可以设置谁先后结束
        //Thread.sleep(2000);//按时间来比不稳定
        System.out.println("main线程结束");

    }
}

在主线程中调用线程对象.join();就是等待线程对象执行完再执行主线程。

:
死等:调用线程对象.join();就会让该线程执行完才继续执行外面的线程,如果线程对象对应的线程一直不结束那么外面的线程就会一直等

超出时间:调用线程对象.join(long millis);就会在该线程执行millis毫秒后执行外面的线程。

如果遇到调用join前线程已经结束,外面的线程不会陷入等待。

线程休眠

前面已经用了很多了,需要注意的是因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。也是因为这个,不能用时间来等待线程结束(不稳定)

在系统让线程休眠sleep中的参数毫秒后,线程会被唤醒从阻塞状态变成就绪状态,但不会马上执行,涉及到调度开销。

并且在Windows和Linux系统上达到毫秒级误差。

获取线程状态

在操作系统里面进程和线程最重要的状态就是:就绪状态阻塞状态

线程的状态是一个枚举类型 Thread.State。

状态说明
newThread对象已经创建,但是start()方法没有调用
terminatedThread对象还在,但是内核中线程已经结束了
Runnable就绪状态,线程已经在CPU上执行或者在CPU上等待执行
timed_waiting由于像sleep(),join(时间) 这种固定时间产生的阻塞
waiting由于像wait(),join() 这种不固定时间产生的阻塞
blocked由于锁竞争产生的阻塞

线程状态的切换通常涉及以下几个关键的操作:

调度:操作系统根据线程的优先级和资源可用性来调度线程。

同步:线程在进入同步代码块或方法时可能需要等待监视器锁。

等待/通知:线程可能需要等待其他线程发出的信号或通知。

超时:某些等待操作有超时限制,超时后线程可能自动恢复到可运行状态。

中断:线程可以被其他线程中断,这可能导致它从等待或超时等待状态中恢复。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值