Java多线程:初识多线程!左手画方,右手画圆

发布时间:2024年01月12日

在这里插入图片描述



嗨!想象一下,你的面前有一张展开的白纸,而你的左手和右手各握一支画笔。你的任务是在同一时间,左手画出一个方形,右手画出一个圆形。你觉得这能够完成吗?我相信大多数人会认为这是不可能完成的任务。因为我们的大脑是单线程的,一次只能专注于一项任务,一心二用在绝大多数情况下是不可行的。然而,计算机不同,它具备多线程编程的能力,可以同时处理多个任务。现在,让我们一起探索Java中多线程这个神奇的功能吧。
在这里插入图片描述

一、线程与进程

进程是计算机中执行中的程序的实例。它是操作系统进行管理和调度的基本单位

线程,有时被称为轻量级进程,是程序中的一个单独的执行流。它是操作系统调度的最小单位。与进程相比,线程之间的上下文切换通常更为迅速和高效。一个线程就是一个 “执行流”。每个线程之间都可以按照顺讯执行自己的代码、多个线程之间 “同时” 执行着多份代码。

  • 进程是操作系统能独立调度的最小单位,而线程是进程中可并发执行的单元;
  • 一个应用程序至少包括1个进程,而1个进程包括1个或多个线程;
  • 每个进程在执行过程中拥有独立的内存单元,而一个进程的多个线程在执行过程中共享内存。

图片描述

那我们来简单的类比一下吧!

image-20230818164204908
计算机的CPU就像一个工厂,而进程就是工厂里面的车间线程就是车间里面的工人

我们能够发现,多核CPU对应多个工厂,这些工厂可以从事不同的生产任务。比如有炼铁的、炼油的、食品加工等等。在一个工厂中,又可以包含一个或多个车间(进程),但是由于电力资源有限,同一时间就只能有一个车间在运行。换句话说,对于单核CPU,同一时间只能处理一个进程,其他进程处在等待状态。而一个车间中,可以有一个或多个工人(线程)协同工作,他们共享内部资源,共同完成任务。

同一个进程中,这些线程共用同一份资源(内存 + 硬盘…),但是每个线程独立去CPU上调度(状态、上下文、优先级、记账信息…)各自有各自的一份

所以,进程是操作系统进行分配的基本单位。而线程是操作系统进行调度执行的基本单位。
在这里插入图片描述
【面试题】谈谈进程和线程的区别和联系

  • 进程是包含线程的,都是实现并发编程的方式,但是线程比进程更轻量。

  • 进程是系统分配资源的基本单位,线程是系统调度的基本单位。创建进程的时候,把分配资源(虚拟地址空间、文件描述符表)的工作给做了,后面创建线程,直接共用资源即可。

  • 进程有独立的地址空间,彼此之间不会相互影响到,进程的独立性。多个线程共用这份地址空间,一个线程一旦抛出异常,就可能会导致进程异常结束。

  • 进程和进程之间不共享内存空间,同一个进程的线程之间共享同一个内存空间。

image-20230818211422931
在这里插入图片描述

二、创建线程方法

在 Java 中,有两种创建多线程的方式,分别是继承 Thread 类和实现 Runnable 接口。下面让我们一起来学习下这两种创建多线程的方法吧。

1、继承Thread

class Mythread extends Thread{
    public void run() {
        System.out.println("这是线程运行代码!");
    }
}

public class Main {
    public static void main(String[] args) {
    
        Mythread mythread = new Mythread();
        mythread.start();
    }
}

创建一个类,继承 Thread ,重写run方法,该方法包含线程的实际执行逻辑。

start()方法:start() 方法是Thread类的一个方法,它用于启动线程并调用线程的run()方法。
在这个例子中,myThread.start() 会启动一个新的线程,并在新线程中执行 run() 方法中定义的代码。

如果把myThread.start();改成myThread.run();后,运行的结果也是hello world。这两个有什么不一样呢?

run只是上面的入口方法,并没有调用系统api也没有创建真正的线程来。

图片描述

2、实现Runnable接口

class Myrunnable implements Runnable{
    public void run() {
        System.out.println("这是线程运行代码!");
    }
}

public class Main {
    public static void main(String[] args) {

        Thread thread = new Thread(new Myrunnable());
        thread.start();
    }
}

该方法主要是创建一个类,实现 Runnable 接口,重写run方法。

3、两者区别

Thread这里是直接把要完成的工作,放到了ThreadRun方法中

Runnable这里,则是分开了,把要完成的工作放到了Runnable中,再让RunnableThread配合。

把线程要执行的任务和线程本身进一步解耦合了。并发编程中,来完成某个工作,使用Runnable描述这个工作的具体细节。这样使用多线程的方式,就可以使用Runnable来搭配线程使用。使用线程池的方式,就可以使用Runnable来搭配线程池使用,使用协议的方式,也可以使用Runnable搭配协程。

特征继承 Thread实现 Runnable 接口
类的继承关系直接继承 Thread 类。类实现 Runnable 接口,可以继承其他类。
创建方式创建类的子类,重写 run() 方法。创建类,实现 Runnable 接口,实现 run() 方法。
多线程对象创建直接创建线程对象的实例。先创建 Runnable 对象,再通过 Runnable 对象创建 Thread 对象。
启动线程直接调用 start() 方法。调用 Thread 类的构造方法,传递 Runnable 对象,然后调用 start() 方法。
资源共享较难实现资源共享。更容易实现资源共享,因为可以共享同一个 Runnable 对象。
多态使用难以使用多态,因为已经继承了 Thread 类。更容易使用多态,因为类可以继续实现其他接口或继承其他类。

4、举个栗子

回到上面的问题。把myThread.start();改成myThread.run();后,运行的结果也是hello world。
这两个有什么不一样呢?我们来举个栗子看看吧。

图片描述

class MyThread extends Thread{
    @Override
    public void run() {
        while(true){
            System.out.println("hello thread");
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        //myThread.run();
        while(true){
            System.out.println("hello main");
        }
    }
}

image-20230818223628990
使用myThread.start();运行的程序,会同时打印hello thread 和 hello main

两个循环都在执行,两个线程分别执行自己的循环,并发性的执行。

而使用myThread.run();运行的程序,只会打印hello main

只执行了一个循环。

在这里插入图片描述

5、简洁写法

我们可以通过匿名内部类来ThreadRunnable这两种写法。
什么是匿名内部类?

匿名内部类是一种在声明和创建类的对象同时进行的特殊形式,它没有显式的类名称。这种方式通常用于在使用类的地方定义类的实例,尤其是在创建接口的实现或继承抽象类的实例时。

Ⅰ、Thread匿名内部类写法

    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println("这是线程运行代码!");
            }
        };
        thread.start();
    }

图片描述

在这里插入图片描述

Ⅱ、Runnable匿名内部类写法

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
            System.out.println("这是线程运行代码!");
            }
        });
        thread.start();
    }

在这里插入图片描述

图片描述

Ⅲ、使用Lambda表达式

Lambda表达式主要用于实现函数式接口,Lambda表达式本质上就是一个匿名函数,像这样的匿名函数可以做回调函数来使用。

图片描述

匿名函数,就是没有命名的函数。它具有传递函数体的能力,却无需提前声明整个函数的结构。

回调函数则是在程序执行中,不需要程序员主动调用的函数。相反,它会在特定的时机或条件下,由系统或其他代码自动触发执行。程序员通过将函数传递给其他代码,实现了一种回调机制,使得在适当的时候,这个函数会被调用,完成特定的操作。

好了,废话不多说,先用Lambda表达式写一下吧。

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("这是线程运行代码!");
        });
        thread.start();
    }

Thread thread = new Thread(() -> {
这里使用Lambda表达式创建了一个新的Thread对象,构造方法接受一个Runnable接口的实现作为参数。Lambda表达式的内容在大括号中,用于定义线程运行时执行的代码块。

图片描述

线程的创建并不是只有这五种写法,还可以用线程池和Callable写法。这个我们后面再聊!

文章来源:https://blog.csdn.net/weixin_61084441/article/details/133620679
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。