多线程基础(保姆级教程)

本文详细介绍了Java中的多线程概念,包括如何通过继承Thread类和实现Runnable接口创建线程,以及start和run方法的区别。还探讨了Runnable接口的优势,匿名内部类和lambda表达式的使用,以及Tread类中的重要方法如线程ID、名称、后台线程、中断和join等。
摘要由CSDN通过智能技术生成

引言:当我们了解进程和线程之后,那就让我们来认识一下什么是多线程吧。


一、创造多线程

1.用Tread类创造线程

方式如下:

public class Mythread extends Thread{

        public void run(){
            while(true){
                System.out.println("hello thread");
            }
        }
}

在类中继承我们的Tread类,重写我们的run方法就能再次创建一个线程了。

⭐️重点解析:

①创造了一个类之后继承Tread类之后,重写run方法是多态的体现

②然而这个run方法也是这个线程入口标志。

🔆这个入口的标志就类似于主方法main()的入口标志。要运行一个java程序,必须有一个进程,进程中最少有一个线程吧。所以在java程序中,main函数也就是一个(主)线程,它是jvm自动自己创造出来的线程。所以主函数线程的入口就是main()方法,run()方法就也是线程的入口啦~

2.调用/启动线程

(1)run

当我们创造出Tread类的时候就有了一个线程,当我们new出这一对象这有了这一线程。那我们应该如何启动这个线程呢?

有人会说直接调用run方法就好了啊。

很好,如果你只调用run方法,只能证明你学过javase。但这并不是真正启动线程的方式!上面已经提及到run方法只是一个入口(描述的是一个任务)当你调用run方法时只能按代码顺序依次执行,当你在run方法加入一个死循环时就再也执行不到main线程里的指令了。

(2)start

所以我们该怎么样才能同时执行到这两个线程或者多个线程呢?

这时候就要用到这个start()方法了。当我们调用到t.start()这个方法时,它就能启动t的这个线程,同时执行下面的主线程。

public static void main(String[] args) {
        Mythread t1 = new Mythread();
        t1.start();
        while(true){
            System.out.println("hello main");
        }
}
    
public class Mythread extends Thread{

        public void run(){
            while(true){
                System.out.println("hello thread");
            }
        }
}

af906cfb3b294743a4fe81a8f5c3b5f8.png

⚠️这里打印的顺序都是随机的,并不是固定的。这是因为多个线程之间的调度顺序是随机的,所以不一定这次执行这个线程下次执行另一个线程。每次每个线程都是随机执行的。

(3)run和start区别

run:描述的只是一个任务,一个入口

start:调用的系统的API,系统内核创造线程


二、Runnable

1.用类实现Runnable接口

上面介绍了继承Tread类创建线程,那我们就来学第二种创建线程的方式实现Runnable接口。

 class MyRunnable implements Runnable {
    public void run() {
        while (true) {
            System.out.println("Hello from MyRunnable");
        }
}

可以看到和Tread类实现方式大差不差。主要区别是通过实现接口的方式来继承类。这样可以更便于我们向上转型的使用。

三、Tread和Runnable

同:

要非说相同点那就都是同在java.lang包下面了。都不需要导包。

区别:

①Tread是继承的关系,它只能单一的继承,不能多个继承。

②Runnable是实现接口的关系,多态是最大的体现。

⭐️⭐️⭐️最大区别再于:解耦合。因为Runnable是一个函数式接口,在Runnable中run方法可以表示为一个可执行程序,可以设置线程和开启线程,任务只是一个任务,启动时可以启动。各司其职,肯定选择第二种方式创建线程更好!由于第一种方式run方法也不是函数式接口最多也就只能用匿名内部类的方式创建,不能用lamdba表示了

四、匿名内部类和lambda

当然我们用匿名内部类和lamdba表达式创建是最简单不过的了

public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                while(true){
                    System.out.println("匿名类内部类创建多线程");
                }
            }
        };
        t1.start();
        while (true){
            System.out.println("主线程");
        }
    }
public static void main(String[] args) {
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("Runnable匿名内部类创建线程");
                }
            }
        });
        t2.start();
        while (true){
        System.out.println("主线程");
    }

ps:注意两者细小的区别

1.Runnable匿名内部类后面有一个小反括号}

2.构造完任务需要一个分号;

 public static void main(String[] args) {
        Thread t3 = new Thread(()->{
                while(true){
                    System.out.println("lamdba表达式创建");
                }
            });
        t3.start();
 }

lamdba表达式后面只需要跟要执行的任务内容就可以了,不需要再重写run方法表示方法的入口。因为lamdba表达式本来就是一种函数式接口,{ }内部就表示一种逻辑。 

所以只有Runnable能实现lamdba表达式,因为它实现的Runnable函数接口的模式;而继承Thread的类就不行了。

五、Tread类中的方法

以下介绍几个重要一点的方法

下面几个构造方法中

1.id

public static void main(String[] args) {
        Thread t3 = new Thread(()->{
        });
        System.out.println(": ID: " + t3.getId());

        t3.start();
        }

id往往是java提供的id 

2.name

public static void main(String[] args) {
        Thread t3 = new Thread(()->{
        });
        System.out.println(": name: " + t3.getName());

        t3.start();
        }

线程中的名字呢往往都是用jconsole调试观察线程

3.后台线程

public static void main(String[] args) {
        Thread t3 = new Thread(()->{
        });
        System.out.println(t3.isDeamon());
        t3.setDeamon();//也可以设置为后台线程
        t3.start();
        }

与之对立的就是前台线程。两者最主要的区别呢就是后台线程不会影响程序的结束,是一个比较独立的线程;而前台线程呢就会影响进程的结束,也就是前台线程结束了,进程也就结束了。

4.存活

public static void main(String[] args) {
        Thread t3 = new Thread(()->{
        });
        System.out.println(t3.isAlive());
        t3.start();
        }

一般来说线程的周期是大于我们内核线程的周期的。

5、sleep

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

sleep是使线程处于休眠状态,是一个类方法,通过输入的数字决定线程休息多少秒。
sleep()一般单位为ms。1000ms=1s。也可用于更好的观察线程的运行。

一般使用sleep都会有报错,我们只需要将这个错误抛出,或者抓住就好了。

六、线程终止interrupt

1.定义

interrupt是终止线程、停止线程的意思。

2.引入interrupt

怎么让一个线程停止呢?一个线程靠run来执行,run执行结束了线程就结束了。所以现在我们只需要让循环提前结束就好了。

第一种方法我们可以手动设置一个标志物,设置一个成员变量,然后在第五秒修改为fales结束循环。

public class demo3 {
    public static boolean isQuite = false;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() ->{
            while (!isQuite) {
                System.out.println("线程工作ing");

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程工作结束");
        });
        t.start();
        System.out.println("主线程启动");
        Thread.sleep(5000);
        isQuite = true;
    }
}

384e0675cb75495a8c333ea26d3ee24d.png

由此可见这种方法是可以的 ,但也有缺陷:需要自己手动设置太麻烦;不能在进入sleep的时候结束循环。

3.interrupt

🌰

    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while ((!Thread.currentThread().isInterrupted())) {
                System.out.println("正在工作ing~");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();;
                }
            }
        });
        t.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();//停止线程
}

我们可以用Tread.currentTread()获取到t这个对象 然后.IsInterrupt()调用方法。当你想终止的时候就可以t.interrupt终止了。

c2d08ec7afa64b25a750700cf3ba4366.png这里终止是终止了,但是另外一个问题又出现了。当我在sleep的过程的抛出了一个异常没错,但是为什么又继续循环了呢?这是因为当你在sleep抛出异常的时候,它会清除你设置的标志位又变成true进入循环开始打印了。所以解决方法就是:①你可以继续执行②你可以在后面break结束循环③或者主动抛出异常处理其他业务。所以这种情况就给我们带来了很大的便利性。而这也仅仅是在唤醒sleep报异常的时候做的处理。

76e8ae4042864aac971a217c6a460e96.png

ps:如果把isquite设成局部变量是否能行呢?这就涉及到lamdba表达式里面的内容了。

答案是肯定不行的,因为变量捕获的原因,内部的那一份变量属于复制的变量,两者是完全不一样的。当要修改常量时当然三是不行的啦~

七、线程等待join

1.定义:

线程等待顾名思义就是让一个线程等待另一个线程结束

栗子

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i<5; i++) {
                System.out.println("t线程正在执行");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        System.out.println("主线程开始执行");
        t.join();
        System.out.println("主线程结束");
    }

在上面栗子中就是让main线程等待t线程结束再结束

这里敲黑板注意了:谁被调用谁被等待。在main方法里调用t.join;就是main线程等待t线程执行结束。

2.作用:

线程等待呢最主要的作用是:控制线程结束时的顺序

八、线程状态

线程一般有下面几种状new,runnable,waiting,timed-wait,terminated,blocked,具体请看大荧幕

681862c2ab584173ad35842c820c3aec.png

看懂这张图你也就大成不就了。虽然挺复杂的,但是挺简单的。

1.就绪

Runable

在cpu上的执行、运行状态或者是准备运行、等待(就绪)的状态。

2.堵塞

一般分为三种:

(1)sleep

在线程上休眠,不占用cpu内存空间。

(2)wait

(3)锁

   后续线程安全讲~

3.终端

trminated

意味线程运行终止运行结束。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值