JavaEE之多线程编程:2.创建线程及Thread类常见方法(超全!!!)

一、创建线程

Java中创建线程的写法有很多种!!!这里介绍其中5种。

方法1:继承Thread类,重写run

创建一个类,让这个类继承自Thread父类,再重写我们的run方法就可以了。

使用Thread类,不需要import别的包,因为它是再Java.lang下面的。

java 复制代码
//写一个类,继承自标准库的Thread
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("hello word!");
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        //创建线程,是希望线程成为一个独立的执行流(执行一段代码)
        //创建线程是相当于雇了个人帮我们干活
        Thread t = new MyThread(); //这里就不用new标准库的thread的了,而是刚才创建的子类
        t.start(); //线程中的特殊方法,启动一个线程
    }
}

注意:

start() 是创建了一个新的线程,由新的线程来执行t.run()方法。

这个新的线程就是调用操作系统的API

通过操作系统内核创建新线程的PCB,并且把要执行的指令交给这个PCB,当PCB被调度到了CPU上执行的时候,也就执行到了线程run方法的代码了。

如果只是在main方法中输出"hello world",你的Java进程主要就是有一个线程(调用main方法的线程),主线程通过t.start(),主线程调用stat(),创建出一个新的线程,新的线程调用t.run(),如果我们run()方法执行完毕,这个线程自然销毁了。

方法2:实现Runnable接口

java 复制代码
//Runnable 作用是描述一个"要执行的任务",run 方法就是任务的执行细节。
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) {
        //这只是描述了个任务
        Runnable runnable = new MyRunnable();
        //把任务交给线程来执行
        Thread t = new Thread(runnable);
        t.start();

    }
}

解耦合。目的就是为了让 线程和任务(线程要干的活)之间分离开。

未来如果要改代码不用多线程,使用多进程、线程池或协程......此时代码改动比较小

方法3:使用匿名内部类,继承Thread

java 复制代码
//使用匿名内部类来创建线程
public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                super.run();
                System.out.println("hello");
            }
        };
        t.start();
    }
}

其中new Thread()

①创建了一个Thread子类,(子类没有名字)所以才叫做"匿名";

②创建了子类的实例,并且让 t 引用指向该实例。

方法4:使用匿名内部类,实现Runable

java 复制代码
public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });
        t.start();
    }
}

这个写法和方法2本质相同,只不过把实现Runable任务交给匿名内部类的语法。

此处是创建了一个类,实现Runable,同时创建了类的实例,并且传给Thread的构造方法。

方法5:使用Lambda表达式

此方法是最简单,最推荐的方法。

java 复制代码
public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("helloDemo5");
        });
        t.start();
    }
}

把任务用lambda表达式来描述,直接把lambda传给Thread构造方法。

lambda就是个匿名函数(没有名字的函数),用一次就没有了。

() - > {}

二、Thread类及常见方法

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

可以说,每个执行流,都需要有一个对象来描述,类似下图,而Thread类的对象,就是用来描述一个线程执行流的,JVM会将这些Thread对象组织起来,用于线程调度,线程管理。

1. Thread的常见构造方法

其中:

最后两个Thread(String name)Thread(Runnable target, String name) 多了个name参数,这个参数是为了方便调试而给线程起了个名字。

线程的默认的名字为 thread-0,1,2之类的。

构造方法的格式如下:

Thread t1 = new Thread();

Thread t2 = new Thread(new MyRunnable());

Thread t3 = new Thread("这是我的名字");

Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

例如:

java 复制代码
public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello");
                }
            }
        },"mythread");
        t.start();
    }
}

2. Thread的几个常见属性

  • ID是线程的唯一标识,不同线程不会重复;

  • 名称是各调试工具用到的;

  • getStat:构造方法里起的名字;

  • getStat:线程状态,Java中线程的状态要比操作系统原生的状态更丰富一些;

  • getPrior:线程的优先级可以获取,也可以设置,但是设置了不管用,因为优先级是很多要素影响得到的;

  • isDaem:是否守护线程,是否"后台线程"

    前台线程会阻止进程结束,前台线程的工作没做完,进程是完不了的;

    后台线程,不会阻止线程结束,后台线程没做完,进程是可以结束的。

    代码里手动创建的线程,默认都是前台线程,包括main,默认也是前台;

    其他JVM自带的线程都是后台线程。

    也可以手动使用setDaemon设置成后台线程,是后台线程,就是守护线程。

    把t设置成 守护/后台线程,此时进程的结束与否就和t无关了。

  • isAlive()是否存活:判断当前系统中的这个线程是不是有了。

    在用户态(应用程序)中创建了一个线程Thread t = new Thread();

    程序中通过t.start();来调用,

    在真正调用start之前,调用t.isAlive(),此时是false,此时内核态(操作系统内核)里没有这个线程;

    而调用start后,就会让内核创建一个PCB,此时这个PCB才表示一个真正的线程,此时isAlive是 true。

    也可以简单的理解为start/run方法是否允许结束了。

    另外,如果内核里线程把run执行完了,此时线程销毁,PCB随之释放。但是Thread t 这个对象还不一定被释放,此时isAlive也是false。

    【总结】

    如果 t.run还没执行,isAlive 为 false

    如果 t,run 正在执行,isAlive 为 true

    如果 t.run执行结束,isAlive 为 false

    通过上述我们可以了解,Thread t这个对象比内核里的PCB存在的周期要久。

3. 启动一个线程------start()

之前我们已经看到了如何通过复写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。

  1. 复写run方法是提供给线程要做的事情的指令清单;
  2. 线程对象可以认为是把李四、王五等新线程叫过来了;
  3. 而调用start()方法,就是相当于喊一声"行动起来!",线程才真正独立去执行了。

总之,run方法是描述了我们要做啥任务,而stat才是真正开始任务。
调用start方法,才真正在操作系统的底层创建出一个线程!

4. 终止一个线程

终止线程的意思是,不是让线程立即就停止,而是通知线程,你应该要停止了,但是是否真的停止,取决于线程这里的具体写法。终止线程有以下两种方式:

  1. 使用标志位来控制线程是否要停止;
java 复制代码
public class ThreadDemo8 {
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException{
        Thread t = new Thread(() -> {
            while (flag) { //当flag为true的时候
                System.out.println("hello!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
            t.start();

            Thread.sleep(3000); //当执行到第三秒的时候,把flag变为false,结束循环
            flag = false;
        }
}

这个代码之所以能够起到作用,修改flag,t线程就结束,完全取决于t线程内部的代码,代码里通过flag控制循环。

因此,这里只是告诉让这个线程结束,这个线程是否要结束,什么时候结束,都是线程内部自己代码来决定的。因为是while(flag)所以3秒钟后结束了,要是while(true),怎么改都不会结束循环。

此方法的缺点:自定义变量这种方式,不能及时相应,尤其在sleep休眠的时间比较久的时候。

  1. 使用Thread自带的标志位( interrupt() 方法),来进行判定是否要停止。

这里调用interrupt,只是通知终止,不是线程一定要乖乖终止!!

【格式】

while (!Thread.currentThread().isInterrupted()) {}

其中:

① while判定,判断条件是true,则执行方法体内的循环,为false,则循环结束;

Thread.currentThread() :这是Thread类的静态方法,通过这个方法可以获取到当前线程。哪个线程调用这个方法,就是得到哪个线程的对象引用(类似于this);

isInterrupted() 为true表示被终止,为false表示未被终止,继续执行;

④ 前面有个 ! 逻辑取反符,所以当isInterrupted() 为true时,!isInterrupted() 就为false,反之,则为true。

interrupt 会做两件事:

① 把线程内部的标志位(boolean)给设置成 true;

② 如果线程在进行sleep,就会触发异常,把sleep给唤醒。

但是sleep在唤醒的时候,还会做一件事,就是把刚在设置的这个标志位,再设置回false。(清空了标志位),这就导致,当sleep的异常被catch完了之后,循环继续执行!

如下述例子:

【例1】:线程t忽视了终止请求

java 复制代码
public class ThreadDemo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        Thread.sleep(3000);

        t.interrupt(); //3秒后,把线程内部的标志位(boolean)给设置成 true
    }
}

此时循环会一直执行下去!这里就是sleep清空的例子。

【例2】:线程t立即响应了终止请求(加了break)

java 复制代码
public class ThreadDemo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        });
        t.start();
        Thread.sleep(3000);

        t.interrupt(); //3秒后,把线程内部的标志位(boolean)给设置成 true
    }
}

【例3】:稍后进行终止

执行完等了3秒钟,代码执行完毕。

java 复制代码
public class ThreadDemo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //稍后再终止!
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                }
                    break;
                }
            }
        });
        t.start();
        Thread.sleep(3000);

        t.interrupt();
    }
}

【总结】

大前提:调用interrupt,只是告诉线程你该终止了,但是它是不是真的终止,这是它自己的事情。

注意其中sleep有个清楚标志位的情况,唤醒之后,线程终不终止,立即还是稍后终止,就把选择全交给程序员自己了。

5. 等待一个线程

有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作,这是我们需要一个方法明确等待线程的结束

可理解为等待一个线程结束!

线程是一个随机调度的过程,等待线程做的事,就是再控制两个线程结束的顺序。

【例1】

java 复制代码
public class ThreadDemo10 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("hello!!!");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t.start();
        System.out.println("join 之前");

        //此处的join就是让当前的main线程来等待t线程执行结束(等待t的run执行完)
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("join之后");
    }
}

执行过程:

t 调用了start()后,t 线程和 main主线程就并发执行,分头行动;

主线程这里打印了"join之前",同时t线程打印了hello thread;

我们的t线程在执行过程中,主线程并没有打印join之后,而是在join这里等待了一会(发生阻塞block),等待3s之后,t线程执行完了,我们的主线程才会执行后面的 "join之后";

主线程,等待t线程彻底执行完毕之后,才继续往下执行了。

通过输出,我们也能看出 t 线程肯定比 main 线程先结束。

【例2】

java 复制代码
public class ThreadDemo10 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("hello!!!");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("join 之前");

        //此处的join就是让当前的main线程来等待t线程执行结束(等待t的run执行完)
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("join之后");
    }
}

在join前加了一个等待5s钟,此时在执行join的时候,t 已经结束了,所以join不会堵塞,就会立即返回。

  • public void join() :无参版本,一直等;
  • public void join(long millis) :指定一个超时时间(最大等待时间),这种是最常见的,一直等很容易又问题。

【总结】

线程没结束,就等待,线程结束了,就立即返回;

总之可以保证这两个线程的返回顺序。

6. 获取当前线程引用

public static Thread currentThread();

返回当前线程对象的引用。

调用这个方法,不需要实例,直接通过类名来调用

java 复制代码
	Thread t = new Thread();
	Thread.currentThread();

在哪个线程中调用,就能获取到哪个线程的实例

7. 休眠当前线程

是我们比较熟悉的一组方法,有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。

public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis

毫秒
public static void sleep(long millis, int nanos) throws InterruptedException 可以更高精度的休眠

java 复制代码
public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(System.currentTimeMillis());
        Thread.sleep(3 * 1000);
        System.out.println(System.currentTimeMillis());
   }
}
相关推荐
游客5205 分钟前
设计模式-创建型-工厂方法模式
开发语言·python·设计模式·工厂方法模式
m0_748239838 分钟前
Python毕业设计选题:基于django的民族服饰数据分析系统的设计与实现_hadoop+spider
python·django·课程设计
m0_748234909 分钟前
Hmsc包开展群落数据联合物种分布模型分析通用流程(Pipelines)
开发语言·python
m0_7482455213 分钟前
Python大数据可视化:基于python大数据的电脑硬件推荐系统_flask+Hadoop+spider
大数据·python·flask
m0_7482370517 分钟前
Python毕业设计选题:基于python的酒店推荐系统_django+hadoop
python·django·课程设计
WongKyunban17 分钟前
bash shell脚本while循环
开发语言·bash
想成为高手49921 分钟前
华为仓颉编程语言的函数与结构类型分析
开发语言·华为
lly20240637 分钟前
Ruby 数据库访问 - DBI 教程
开发语言
星就前端叭43 分钟前
【开源】一款基于SpringBoot的智慧小区物业管理系统
java·前端·spring boot·后端·开源
带刺的坐椅43 分钟前
RxSqlUtils(base R2dbc)
java·reactor·solon·r2dbc