C C++最新【JavaEE初阶】多线程 _ 基础篇 _ 线程的概念和创建,C C++初级面试题2024

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

二、多线程程序

即使是一个最简单的 hello world,其实在运行的时候也涉及到 “线程” 了~

一个进程里面至少有一个线程~

运行这个程序,操作系统就会创建一个 Java进程,在这个 Java进程 里就会有一个线程(主线程)调用 main方法~

虽然在上述代码中,我们没有手动的创建其他线程,但是 Java进程 在运行的时候,内部也会创建出多个线程~

在谈到多进程的时候,会经常谈到 “父进程” 和 “子进程”,如果在 A进程 里面创建了 B进程,那么A 是 B 的父进程,B 是 A 的子进程~

但是,在多线程里,没有 “父线程” 和 “子线程” 的说法,因为我们认为 线程之间的地位是对等的~

2.1 第一个Java多线程程序

Java中创建线程,离不开一个关键的类 —— Thread;

一种创建线程的方式,是写一个子类,继承 Thread,并且重写其中的 run方法:

当然,如果仅仅是创建了一个类,还不可以说是 创建了线程,还得要创建实例才可以:

可以这样来理解:

重写的 run方法 —— 先把新员工的任务准备好;

Thread t = new MyThread(); ——  招聘来了一个新员工 t,把任务交给他了(但是还没有开始干活);

t.start(); —— 开始干活~

即:使用 new 创建线程对象,线程并没有被创建,而是仅仅创建了一个线程对象,运行 start方法 时才会创建线程,并执行 run方法~


--第一个Java多线程程序的代码

package thread;

class MyTread extends Thread {
    @Override
    public void run() {
        //run方法本来是 Thread内部所提供的方法
        //这个 run方法 重写的目的,是为了明确,新创建出来的线程,是要干什么的
        System.out.println("hello thread");
    }
}
public class Demo1 {
    public static void main(String[] args) {
        //创建MyThread线程对象,但是线程没有创建
        Thread t = new MyTread();
        t.start();
        //t.start() 才是真正的开始创建线程,
        // 在操作系统内核中,创建出对应线程的 PCB,然后让这个 PCB 
        // 加入到系统链表中 参与调度,出现的线程就会执行上面的 run方法
    }
}


运行结果:

2.2 怎么样观察线程的详细情况

如果我们此时在 主函数中添加这样一句代码:

那么,此时的运行结果是:

明明是先执行了线程,后打印的"hello main",但是 为什么结果却是 先打印出来 “hello main”,后打印的"hello thread" 呢?

  1. 每个线程都是独立的执行流!换句话说,main对应了一个执行流,MyThread对应了另一个执行流,这两个执行流之间是 并发 的关系~
  2. 此时两个线程执行的先后顺序,取决于操作系统 调度器 的具体实现~

程序猿可以把这里的调度规则 简单的视为 “随机调度”,这个是改变不了的~

如果是想要控制哪个线程先执行,最多是让某个线程先等待,让另一个线程执行完了再执行~

所以,当程序运行的时候,先看到哪一个被执行的顺序 是不确定的,

虽然 可以在这里运行了许多次,先打印出来的是"hello main",但是顺序仍然是不可确定的,大概率是受到了创建线程自身的开销影响的~

当执行结果中出现了这一句话,就说明 进程已经结束了,并且退出码是 0:

就像 C语言中的 return 0~

操作系统中用 进程的退出码 来表示进程的运行结果:

使用0表示进程执行完关闭,结果正确;使用 非0 表示进程执行完关闭,结果不正确;还有一种情况是 main还没有返回,程序就崩溃,此时返回的值很可能是一个随机值~

当然,如果想要使进程不要结束的那么快,可以在 main方法 和 重写的run方法 使用死循环,让它们一直打印,这样就可以了~

然后再执行结果中 是:“hello main” 和 “hello thread” 在交替打印,每一波都会打印几个,然后再打印下一波,当然 都是不确定的,打印那个内容,也都是调度器在进行控制~

此时,就可以来查看 当前Java进程里面的线程的情况~

可以在任务管理器中 看见Java进程的情况(需要把死循环的代码运行起来,不然嗖的一下就没了):

当然,此时是看不到 Java线程的,需要借助其他的工具~

在 JDK 里,提供了一个 jconsole 这样的工具,可以看到 Java进程里面的线程的详情~

运行 jconsole 之后,就可以看到 线程的情况了:

如果在打开 jconsole 之后,如果显示不到 本地进程的管理列表,那么可以退出,然后右键 选择使用管理员的方式运行~

2.3 sleep方法

如果想要线程来适当的 “休息” 一下,为了方便观察,不要让刚刚的死循环代码 打印 “hello main” 和 “hello thread” 打印的太多太快,我们可以用 sleep 来进行操作~

sleep 是 “休眠” 操作,指定让线程摸一会儿鱼,不要上 CPU 上干活,参数单位是 毫秒~

使用 Thread.sleep 的方式进行休眠,sleep 是 Thread 的静态成员方法,直接通过 类名.方法名 的方式调用~

时间单位的换算:

1秒 = 1000毫秒,1毫秒 = 1000微秒,1微秒 = 1000纳秒,1纳秒 = 1000皮秒~

秒(s)、毫秒(ms)、微秒(us)、纳秒(ns)、皮秒(ps)~

由于计算机算得快,所以常用的单位是:ms、us、ns这几个单位~

Interrupted 中断!!!

sleep(1000)就是要休眠 1000毫秒,但是 在休眠过程中,可能有一点点意外 把线程给提前唤醒 —— 该异常唤醒的~

当然,用同样的方法 处理一下 main方法里面的,就可以很清楚的看到 最终打印的结果是按照自己设定的样子进行的(博客写不了按照时间运行的过程,就不去展示了)~

2.4 run 和 start 方法的区别是什么

所以我们可以很清楚的看到,直接调用 run方法,并没有创建新的线程,而只是在之前的线程中,执行了 run方法里面的内容;使用 start方法,则是创建了新的线程,新的线程里面会调用 run方法,新线程和旧线程是并发执行的关系~

三、创建线程

3.1 继承 Thread类

如上面所介绍过的,创建一个类 继承 Thread,再重写 run方法~

package thread;

class MyTread extends Thread {
    @Override
    public void run() {
        //run方法本来是 Thread内部所提供的方法
        //这个 run方法 重写的目的,是为了明确,新创建出来的线程,是要干什么的
        while(true){
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
public class Demo1 {
    public static void main(String[] args) {
        //一种比较朴素的创建线程的方式,是写一个子类,继承 Thread,重写其中的 run方法
        Thread t = new MyTread();
        t.start();
        }
    }
}

说明:

这个写法,线程 和 任务内容 是绑定在一起的~

3.2 实现 Runnable接口

创建线程,还可以创建一个类,实现 Runnable接口,再重写 run方法~

package thread;

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

    }
}
public class Demo2 {
    public static void main(String[] args) {
        //创建线程
        Runnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();

        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:


分析:

此处创建的 Runnable,相当于是定义了一个 “任务”(代码要做什么),

还是需要 Thread实例,把任务交给 Thread,

还是需要 Thread.start 来创建具体的线程~

说明:

这个写法,线程和任务是分离开的,可以更好的解耦合,“高内聚 低耦合”,因此 使用实现 Runnable接口的方法更优~

把任务内容 和 线程 本身分离开了,即 任务的内容和线程的关系不大~

假设这个任务不想通过多线程的方式执行了,想通过别的方式来执行,这个时候代码改动也不大~

3.3 使用 匿名内部类 来创建线程

我们也可以仍然继承 Thread类,但是不在是显式继承,而是使用 “匿名内部类” 来创建线程~

package thread;

public class Demo3 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t.start();
    }
}

运行结果:


分析:

红色框框里面的内容,创建了一个匿名内部类(没有名字),这个匿名内部类是 Thread 的子类,同时前面的 new 关键字,就会给这个匿名内部类创建出了一个实例~

这一套操作,继承、方法重写、实例化 一条龙服务~

在 start 之前,线程只是准备好了,并没有真正的被创建出来,执行了 start方法,才真正在操作系统中创建了线程~

Thread 实例是 Java 中对于线程的表示,实际上要想正真跑起来,还需要操作系统里面的线程~

创建好了 Thread,此时操作系统里面还没有线程,直到调用 start方法,操作系统才真的创建了线程(创建 PCB,并且把 PCB 加入到链表里),并且进行执行起来~

3.4 使用Runnable接口,以匿名内部类的方式创建线程

package thread;

public class Demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();
    }
}

运行结果:

3.5 使用Lambda表达式创建线程(推荐做法)

package thread;

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

运行结果:

说明:

使用 lambda表达式,其实是更简单的写法,也是推荐写法;

形如 lambda表达式这样的,能够简化代码编写的语法规则,称为 “语法糖”~

实际上,线程还有其他的创建方式~

如:基于 Callable/Future Task 的方式创建,基于 线程池 的方式创建…

这些留在以后的方式来介绍~

四、多线程的优点

单个线程,串行的,完成 20 亿次自增~

package thread;

public class Demo6 {
    private static final long count = 20_0000_0000;

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

线程还有其他的创建方式~

如:基于 Callable/Future Task 的方式创建,基于 线程池 的方式创建…

这些留在以后的方式来介绍~

四、多线程的优点

单个线程,串行的,完成 20 亿次自增~

package thread;

public class Demo6 {
    private static final long count = 20_0000_0000;

[外链图片转存中…(img-RDANdBBP-1715724697093)]
[外链图片转存中…(img-gCBggLnG-1715724697094)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 11
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值