JavaEE - 创建线程的几种方式(start()与run()有什么区别)、多线程的调度顺序、多线程的使用场合

目录

一、怎么创建线程?

🟣 方式一:继承Thread类,重写run方法

🟣 方式二:实现Runnable接口重写run方法

【代码细节:比较方式一 & 方式二】

【代码细节: start方法和run方法】

🟣 其他方法

二、多线程的调度(执行)顺序

【 注意:创建顺序&执行顺序】

三. 多线程的使用场合


为什么需要线程?

并发编程虽然能够使得各个进程合理的占用到CPU的资源,提高运行速度。但是创建和销毁进程的过程(创建PCB -> 分配系统资源(耗时) -> 把PCB加入到内核的双向链表中....)实际上比较低效,当我们需要频繁创建/销毁进程的时候就不够满足需求了...

为了提高这个场景的效率,操作系统引入线程...

一、怎么创建线程?

创建线程,需要用到Thread类。下面这两个创建线程的方法就是线程类Thread中的两个构造方法..是我们创建线程的两种经典方法,其他创建方式基本建立在这两种基础之上...

🟣 方式一:继承Thread类,重写run方法

java.lang.Thread类的对象就可以当作一个独立的线程来使用。

创建一个Thread类的对象,然后调用该对象的start()方法,这个对象就会到CPU前排队等待运行,得到CPU就会运行它的run()方法。

Thread类的run()方法是空白的,通过继承Thread类来进行多线程编程,关键在于重写run()方法。

代码示例: 

package learn;

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("新建线程");//分配任务,让线程明知道创建出来是要干什么
    }
}
public class demo1 {
    public static void main(String[] args) {
        Thread thread=new MyThread();//新建线程对象
        //也可以使用Thread thread=new Thread();
        thread.start();//创建线程
    }
}

🟣 方式二:实现Runnable接口重写run方法

java.lang.Runnable接口声明了唯一的方法 run();

Thread类有个构造方法 Thread(Runnable r)。当用这个构造方法创建一个Thread类对象时,必须以一个实现了Runnable接口的对象r为参数;

调用这个Thread类对象的start()方法,它就是一个独立运行的线程,Thread类的run()方法是空白的,它实际运行r对象的run()方法。

代码示例:

package learn;

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("新建线程");
    }
}
public class demo2 {
    public static void main(String[] args) {
        MyRunnable runnable=new MyRunnable();//创建Runnable对象
        Thread thread=new Thread(runnable);//将创建出来的runnable对象传入Thread构造方法中
        thread.start();//创建线程
    }
}

【代码细节:比较方式一 & 方式二】

为了便于代码的扩展与维护,我们写代码时要注意 “低耦合” 。

方式二将线程的创建分成两步走,对比方式一就比较低耦合:

MyRunnable runnable=new MyRunnable() -> Thread thread=new Thread(runnable)

🧐 低耦合的优点

我们写的类其实是封装使用的,写好的类相当于一个接口。下次创建线程直接用这个接口类直接new个对象就可以了。不过有时候我们需要对类进行维护、扩展:比如给线程类扩展一个构造方法,让用户new对象时,给线程传入一个字符串作为线程的名字。

方式一在线程Thread类中增加构造方法后,new对象的方式需要改成 Thread thread=new Thread("字符串")。新的扩展就影响了之前写的代码,就要把之前的写的new都改了!

方式二改变Runnable类中的方法,对之前写过new对象的代码就没有影响 

【代码细节: start方法和run方法】

创建Thread实例并没有在操作系统中创建出线程,只是通过run方法在给操作系统安排任务。

1. start方法的作用是真正的去申请系统线程

调用start方法将在操作系统中创建一个新线程,使之从最初创建Thread类的新建状态进入就绪状态。注意:此时线程处于就绪状态但并没有运行,要真正运行起来还得看操作系统有没有调度这个线程,执行这个线程的任务。

2. run方法的作用是描述线程具体要执行的任务

run方法只是thread的一个普通方法其中包含了当前要执行的这个线程的内容run()和其他普通方法一样,如果有代码段调用它,它就会执行。

对比一下几组代码,使用run与start方法,观察两者的作用:

① 使用start方法

使用start( )则会创建一个新线程,系统调度该线程时执行run( )。此时的run()由新线程调用。

🙋🏻‍♀️这里有两个线程:主线程 & 新线程 t

线程和线程之间并发执行,在各自的线程中执行对应的线程信息。

② 使用run方法

不使用start( ),在主线程中直接调用run( )。

🙋🏻‍♂️ 如果不使用start(),只创建了一个Thread实例,意味着没有创建新线程。

run()是一个普通方法,这里在主线程中调用run方法。主线程启动,会顺序执行一次run()

🟣 其他方法

以下几种方法其实是以上两种方法的代码简化版本,使得代码更加简洁...

1. 使用匿名内部类

public class demo3 {
    public static void main(String[] args) {
        Thread thread=new MyThread(){
            @Override
            public void run() {
                System.out.println("新建线程");//重写run方法
            }
        };
        thread.start();//创建线程
    }
}
public class demo4 {
    public static void main(String[] args) {
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println("新建线程");
            }
        };
        Thread thread=new Thread(runnable);
        thread.start();
    }
}

 2. 使用 lambda表达式

public class demo5 {
    public static void main(String[] args) {
        Thread thread=new Thread(()->{
            System.out.println("新建线程");
        });
        thread.start();
    }
}

二、多线程的调度(执行)顺序

在我们写的java代码中,就有多个线程比如代码中能看见的主线程和我们自己写的新建线程。还有些代码中看不见但是可以通过工具看到的其他线程,这就是多线程...

主线程是当一个程序启动时操作系统自动创建的线程。

为了观察多线程之间的调度顺序,我们在新线程和主线程当中写一个循环打印输出 。

代码示例:

public class demo3 {
    public static void main(String[] args) {
        //新建线程的代码段
        Thread thread=new MyThread(){
            @Override
            public void run() {
                while(true){ //为了让线程执行的久一点,让我们看到执行的顺序。我们可以使用循环
                    System.out.println("a线程");
                    try {
                        Thread.sleep(1000);//代码执行的顺序非常迅速,使用sleep可以让执行的过程慢下来
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();//创建线程
        //主线程中的代码段
        while(true){
            System.out.println("主线程");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出结果: 

观察发现:线程和线程之间的执行顺序不确定。这是因为线程和线程之间并发执行自己的任务,执行顺序由调度器决定(可以认为是随机的)。

🔊 jdk当中有一个工具jconsole,可以让我们看到正在运行进程中的线程。

注意:用这个工具的时候,要保证程序一直在运行哦~ 比如我们上面写的代码就是使用了while循环,让程序一直运行

① 找到 jconsole 工具。每个人下载安装jdk的包可能不同,所以路径可能不同... 

②选择进程

③ 查看线程

【 注意:创建顺序&执行顺序】

创建顺序与执行顺序不要搞混

主线程是由 jvm 自动创建,在主线程中每次的start()就是在创建一个线程,创建顺序由程序员决定

主线程和其他线程并发执行,执行顺序由调度器决定

并不是只有主线程才能创建线程, 被创建出来的线程同样可以创建线程。

三. 多线程的使用场合

  • 对于一些IO密集型任务,使用多线程可以提高效率

IO密集型任务指的是读写操作频繁。任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度),这种情况下,CPU利用率低。这时候就可以使用多线程启动多任务,利用好CPU的空闲时间,提高利用率加快速度。

  • 对于一些CPU密集型,可以充分利用CPU多核资源提高效率

CPU密集指的是计算频繁。

注意:多线程多任务虽然有优势但是也有劣势

如果两个线程修改了通过一份变量,将导致结果不准确;线程也不是越多越好,线程过多,一拥挤就可能出现线程异常,不安全。(线程安全问题下一篇博客会提到,可以去看看哦~)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值