【JavaSE】多线程篇(一)线程的相关概念与线程的基本使用

简介: 文章目录1 线程的相关概念2 线程的基本使用2.1 继承Thread类2.1.1 为什么使用start而不直接调用run?2.1.2 追进start源码2.2 实现Runnable接口2.2.1 实现Runnable底层机制浅析2.3 继承Thread类与实现Runnable接口的区别写在最后

1 线程的相关概念

程序(program): 指为完成特定任务、用某种语言编写的 一组指令的集合。

进程: 进程是指 运行中的程序, 比如我们平时使用微信,就相当于启动了一个进程,操作系统就会为该进程分配内存空间。进程是程序的一次执行过程,或是正在运行的一个程序。是 一种动态过程,有自身的产生、存在和消亡的过程。



线程: 线程是由进程创建的,是进程的一个实体。一个进程可以拥有多个线程。 比如日常我们使用视频软件,就是打开了一个进程,而在该软件中同时下载多个视频,每个下载视频的任务就可以看成一个进程。

单线程: 同一个时刻,只允许执行一个线程。

多线程: 同一时刻,可以执行多个线程。

并发: 同一时刻,多个任务交替进行, 造成一种“貌似同时”的错觉,简单的说,单核CPU实现的多任务就是并发。

并行: 同一时刻,多个任务同时执行, 多核CPU可以实现并行。并发并行可以同时存在。

🐱 下面的例子中获取了当前电脑cpu的核心数量:

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 获取当前电脑的cpu核心数
 */
public class ThreadTest01 {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();
        // 获取当前电脑cpu数量/核心
        int cpuNums = runtime.availableProcessors();
        System.out.println("当前电脑的cpu核心数:" + cpuNums);
    }
}

2 线程的基本使用

2.1 继承Thread类

第一种使用方式为,继承Thread类,并重写run方法


当一个类继承了Thread类,该类就可以当作线程使用;

重写run方法,在里面会写上自己的业务代码;

Thread类实现了 Runnable接口 的run方法。



🦁 案例演示:


(1)编写一个程序,开启一个线程,该线程每相隔1秒,在控制台输出:“我是大黄”;

(2)进行改进,当输出10次时,结束该线程;

(3)使用JConsole监控线程执行情况,并画出程序示意图。

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 通过继承Thread使用线程
 */
public class ThreadTest02 {
    public static void main(String[] args) {
        // 创建一个DaHuang对象,可以当作线程使用
        DaHuang daHuang = new DaHuang();
        daHuang.start();  // 启动线程
    }
}
class DaHuang extends Thread{
    /**
     次数
     */
    int times = 0;
    @Override
    public void run() {// 重写run方法,自己的业务逻辑
        while (true) {
            System.out.println("我是大黄" + (times + 1) + "次");
            times++;
            // 休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 10){
                break;
            }
        }
    }
}


在程序中,我们可以使用 Thread.currentThread().getName()来获取当前线程的名称。上述程序 执行过程中,会打开一个进程,进入main主线程。当执行start时,会执行Thread-0子线程。 示意图如下:



需要特别注意的是,Thread-0子线程的执行,并不会造成main主线程的阻塞! 修改主方法如下,结果可以看到,两个线程都在执行:

    public static void main(String[] args) {
        // 创建一个DaHuang对象,可以当作线程使用
        DaHuang daHuang = new DaHuang();
        daHuang.start();  // 启动线程
        for (int i = 0; i < 10; i++) {
            System.out.println("i = " + i + ", 线程" + Thread.currentThread().getName());
        }
    }


2.1.1 为什么使用start而不直接调用run?

上述代码中,使用start后,会调用run。但是,如果通过直接使用run方法,则是由主线程调用的!代码如下:

public static void main(String[] args) {
        // 创建一个DaHuang对象,可以当作线程使用
        DaHuang daHuang = new DaHuang();
        daHuang.run();
    }


此时,由于run是主线程调用的,那么,就不是真正意义的多线程。run仅仅是一个普通的方法,没有真正的启动一个线程。 在主线程中,必须要当前run这条语句的动作执行完毕,才能继续向后执行,造成了阻塞。


2.1.2 追进start源码

start启动后,会进入到start方法,该方法最核心的,是执行了start0方法:



start0是本地方法,是由jvm调用的,底层是c/c++实现的,真正实现多线程效果的,是start0方法:


start方法调用start0方法后,该线程并不一定会立马执行,只是将线程变成了可运行状态。 具体什么时候执行,取决于CPU,由CPU统一调度。



2.2 实现Runnable接口

由于java是单继承的,在某些情况下,一个类可能已经继承了某个父类,这时再用继承Thread类的方法来创建线程显然是不可能的了。

因此,java设计者,提供了另一种方法,就是通过实现Runnable接口来创建线程。

🦁 案例演示:


请编写程序,该程序可以每隔1秒,在控制台输出 “你好!”,当输出5次后,自动退出。请通过实现Runnable接口的方式来实现。

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 通过实现Runnable接口的方式创建线程
 */
public class ThreadTest03 {
    public static void main(String[] args) {
        Say say = new Say();
        // 创建Thread对象,把say对象(实现了Runnable),放入Thread
        Thread thread = new Thread(say);
        thread.start();
    }
}
class Say implements Runnable{
    int times = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("你好!" + (++times));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 5){
                break;
            }
        }
    }
}

2.2.1 实现Runnable底层机制浅析

使用实现Runnable的方式创建线程,底层使用了设计模式——代理模式。

🐘 简单模拟:

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 */
public class ThreadTest03 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        //dog实现了Runnable接口
        ThreadProxy threadProxy = new ThreadProxy(dog);
        threadProxy.start();
    }
}
/**
 * 线程代理类
 */
class ThreadProxy implements Runnable{
    /**
     * 属性,类型为 Runnable
     */
    private  Runnable target = null;
    @Override
    public void run() {
        if (target != null){
            target.run();//动态绑定(运行类型)
        }
    }
    public ThreadProxy(Runnable target){
        this.target = target;
    }
    public void start(){
        start0();
    }
    public void start0(){
        run();
    }
}
class Animal{}
class Dog extends Animal implements Runnable{
    @Override
    public void run() {
        System.out.println("Dog 汪汪汪");
    }
}


🐱 说明:


因为Dog类实现了Runnable接口,所以dog对象可以传入ThreadProxy的构造器;

此时,调用start会调用ThreadProxy的run方法;

由于动态绑定机制,运行类型的dog,最终会调用dog的run方法。


2.3 继承Thread类与实现Runnable接口的区别

从java的设计上看,通过继承Thread类与实现Runnable接口的方式来创建线程本质上没有区别,从jdk文档中,我们可以知道,Thread类本身就实现了Runnable接口;

实现Runnable接口的方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制。


相关文章
|
11天前
|
调度 开发者
核心概念解析:进程与线程的对比分析
在操作系统和计算机编程领域,进程和线程是两个基本而核心的概念。它们是程序执行和资源管理的基础,但它们之间存在显著的差异。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
26 4
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
23 3
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
20 2
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
34 2
|
2月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
39 1
|
2月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
42 1
|
2月前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
28 1
|
22天前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
55 0
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
51 1
C++ 多线程之初识多线程
|
2月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
64 6