一、线程的创建

1.1 方法一  继承Thread类

继承Thread重写run 来创建一个线程类

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("This is Thread");
    }
}

创建MyThread类的实例   调用start方法启动线程

 public static void main(String[] args)  {
        //向上转型
        Thread t = new MyThread();
        //真正在系统中创建出一个线程
        t.start();
        System.out.println("this is main");
    }

此时看线程的代码执行结果好像和普通代码的结果没什么区别 并没有很好体现出线程的用法和效果。

给MyThread的输入加入循环

看结果好像就只有MyThread在输出 但是结果并如此

程序里一共两个线程:1.main 线程  2.t 线程

实际情况是:

1.System.out.println("this is main"); 是执行了的,只执行了一次,但几乎看不到。

2.子线程“刷屏”把 main 的输出淹没了

这是最核心的原因

  • t 线程在 while(true)不休眠

  • CPU 时间几乎都被它抢走

  • 控制台被 This is Thread 疯狂刷屏

改良1.给MyThread循环中加入sleep

可以看的main线程是有执行的

改良2.main线程加入循环

从这块可以看出线程的作用特点  普通程序进入死循环的时候是跳不出来的  start创建了一个新的线程,多了一个执行流,能够干活(这个代码就可以"一心两用" 同时做两件事)

给两线程都加入sleep 

这俩线程,谁先执行,谁后执行,都有可能,无法预测(编写代码,也不能依赖这俩逻辑的执行顺序)

操作系统内核的调度器,控制的没法在应用应用程序中编写代码控制(调度器没有提供api 的)唯一能做的就是给线程设置优先级(但是优先级,对于操作系统来说,也是仅供参考,不会严格的定量的遵守)

方法一特点:1.简单直观 2.不能再继承其他类(Java 单继承)

1.2 方法二  实现Runnable 接口

实现Runnable接口

class MyRunnable implements Runnable {
    @Override
    public void run() {
        while (true){
            System.out.println("this is thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

创建了一个 Runnable 任务对象,并将该任务交给 Thread 线程对象,由该线程在启动后执行该任务。

public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();
        while (true){
            System.out.println("this is main");
            Thread.sleep(1000);
        }

    }

方法二特点:1.可以继续继承别的类  2.线程和任务解耦

1.3 方法三 使用匿名内部类

Thread t = new Thread(){

};

做了三件事

1.创建了一个Thread的子类。子类叫啥名字?不知道!!匿名!

2.(里面就可以编写子类的定义代码.子类里要有哪些属性,要有哪些方法重写父类的哪些方法.....

3.创建了这个匿名内部类的实例并且把实例的引用赋值给t.

 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(){
            @Override
            public void run() {
                while(true){
                    System.out.println("this is thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        t.start();

        while (true){
            System.out.println("this is main");
            Thread.sleep(1000);
        }
    }

这样就可以少定义一些类了.一般如果某个代码是“一次性"就可以使用匿名内部类的写法。

1.4 方法四 使用Runnable 匿名内部类

public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("this is thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        Thread t = new Thread(runnable);
        t.start();

        while (true) {
            System.out.println("this is main");
            Thread.sleep(1000);
        }
    }

1.5 方法⑤ 针对三和四进一步改进,引入lambda表达式

本质上就是一个"匿名函数最主要的用途,就是作为回调函数”

大部分推荐方法五的写法

二、Thread的常见构造方法

2.1Thread(Runnable target, String name)使用Runnable对象创建线程对象,并命名

2.2 Thread的几个常见属性

1.isDaemon() 是否后台线程

啥是后台线程 再看回上述代码的线程图分析

红色括起来的这些是JVM自带的线程,他们的存在 不影响进程结束(即使他们继续存在,如果进程要结束了,他们也随之结束了)
这种就是后台线程

而框起来的t1,t2,t3这几个线程的存在,就能够影响到进程继续存在这样的线程,就称为前台线程”

前台线程,后台线程,都是有多个的.如果有多个前台线程,必须得所有的前台线程都结束,进程才结束

2. isAlive() 是否存活

Java代码中创建的Thread对象,和系统中的线程,是一一对应的关系。但是,Thread对象的生命周期,和系统中的线程的生命周期,是不同的(可能存在,Thread对象还存活,但是系统中的线程已经销毁的情况)

public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t.start();
        while (true){
            System.out.println(t.isAlive());
            Thread.sleep(1000);
        }
    }

3. isInterrupted() 是否被中断

三、线程中断

public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("this is thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //情况一 这个线程掀桌了
                    //throw new RuntimeException(e);
                    //情况二 1.加入break就是立即终止
                    //break;
                    //情况三 2.啥都不写就是不终止

                    }
                }
                System.out.println("t 结束");
            });
            t.start();

            Thread.sleep(3000);
            System.out.println("main线程 尝试终止t线程");
            t.interrupt();
    }

1.情况一

线程一:输入一次休息一秒
线程二:等待3秒  3秒到了执行t.interrupt(把循环的false改成true

此时 JVM立刻检查一个特殊规则 JVM发现:t线程:正在 sleep interrupt flag == true

JVM规定:如果线程在 sleep/wait/join中并且被 interrupt必须立刻抛 InterruptedException
抛出异常线程中断

异常终止线程(不优雅)使用RuntimeException强制结束
违背了中断的设计初衷

2.情况2

与上述过程一样 不过在catch中 直接break跳出循环

3.情况3

catch什么都不做
1.interrupt设置中断标志while循环的条件 变成!true
2.JVM 发现线程正在 sleep根据JVM 规则:sleep 中被 interrupt - 抛 InterruptedException
抛异常前,会把 interrupt flag 清成false循环条件再次变成!false
3.进入catch块
啥也没干

针对上述代码
其实是sleep在搞鬼
正常来说。调用Interrupt-方法就会修改isInterruptted方法内部的标志位 设为true

由于上述代码中,是把sleep给唤醒了,这种提前唤醒的情况下,sleep就会在唤醒之后,把isInterruptted 标志位给设置回false

因此在这样的情况下,如果继续执行到循环的条件判定,就会发现能够继续执行

四、join() 线程等待

为什么需要线程等待呢?

多个线程之间 并发执行 随机调度.
站在程序员的角度,咱们不喜欢随机的东西join能够要求,多个线程之间,结束的先后顺序.

比如,在主线程中调用tjoin就是让主线程等待t线程先结束.虽然可以通过sleep休眠的时间,来控制线程结束的顺序但是,有的情况下,这样的设定并不科学

有的时候,就是希望,t先结束,main就可以紧跟着结束了,此时通过设置时间的方式,不一定靠谱

public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 300; i++) {
                System.out.println("this is thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("t 线程结束");
        });
        t.start();

        //Thread.sleep(3000);
        t.join();
        System.out.println("main 结束");
    }

在 main线程中,调用t.join效果:让main线程等待t先结束当执行到t.join,此时main线程就会"阻塞等待”一直等到t线程执行完毕,join才能继续执行只要t线程不结束,主线程的join就会一直一直的等待下去

join提供了带参数的版本,指定“超时时间"
等待的最大时间

如果3000之内,比如刚过了1000,t就结束了,此时join立即继续执行(不会等满3000)如果超过3000,t还没结束,此时join也继续往下走,就不等了

五、线程休眠

线程休眠其实在线程的时候就一直在使用了

六、获取线程实例

获取当前线程

获取指定线程

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐