多线程(初阶)——多线程基础

简介: 多线程(初阶)——多线程基础

多线程(初阶)——多线程基础

文章目录

1.认识线程

首先我们得知道线程是什么

进程内的执行单元,不分配单独的资源,执行一个单独的子任务。

线程是进程内调度和分派的基本单位,共享进程资源。每个线程有自己的独立的栈存储空间,保存线程执行的方法以及基本类型的数据。

为啥会有线程

  1. 我们需要进行“并发编程”(CPU单个核心已经发展到了极致,无法满足当前编程需求,想要提高算力,就要使用多个核心)
  2. 引入并发编程的目的就是更充分利用多核CPU资源
  3. 使用多线程可以做到并发编程,并且能够使CPU多核被充分利用
  4. 虽然多进程也能实现并发编程,但是线程比进程更轻量
  • 创建线程比创建进程更快
  • 销毁线程比销毁进程更快
  • 调度线程比调度进程更快
  • 一个线程包含在进程之中(一个进程中可以有多个线程)

当我们创建一个进程时

  1. 创建PCB
  2. 分配系统资源(尤其是系统资源)——特别消耗时间
  3. PCB加入的内核的双向链表中

而创建线程时

  1. 创建PCB
  2. PCB加入到内核的双向链表中

节省了大量资源分配的时间,提高了效率


2.多线程程序

2.1 第一个Java多线程程序

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

JDK中提供了一个叫Thread的类,通过Thread类创建对象,就可以创建一个线程

public class Demo1 {
    public static void main(String[] args) {
        MyTread myTread = new MyTread();
        myTread.start();
    }
}
class MyTread extends Thread {
    @Override
    //重写run方法
    public void run() {
        System.out.println("thread...");
    }
}

aa8f1facd5967a442080cfaa182a75d6.png

重写run方法,就是去指定线程要执行的任务

MyTread myTread = new MyTread();创建自己的线程对象,本质就是Java类的一个实例myTread.start();这个方法让JVM去操作申请一个真实的PCB,这就与操作系统扯上关系了,操作系统就能执行我们自己定义的这个线程任务了。

2.2 观察线程的详细情况

当我们在主函数中添加一句这样的代码

public class Demo1 {
    public static void main(String[] args) {
        MyTread myTread = new MyTread();
        myTread.start();
        System.out.println("main...");
    }
}
class MyTread extends Thread {
    @Override
    //重写run方法
    public void run() {
        System.out.println("thread...");
    }
}

此时运行结果是

5e2b2d6d0e97445615c600a651157a06.png

代码先执行的是myTread.start();,但为什么先打印的是“main…”,而不是先打印“thread”呢?

public class Demo1 {
    public static void main(String[] args) {
        MyTread myTread=new MyTread();
        myTread.start();
        //在死循环中做打印
        while (true){
            System.out.println("main...");
            try {
                MyTread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
//通过继承Tread类的方式创建一个线程
class MyTread extends Thread{
    @Override
    public void run() {
        while (true){
            System.out.println("thread...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Thread.sleep(1000);让线程休息一会

当代码进行死循环打印时会出现下面的结果

c3d4cbb4861d43a153f4e95898da7e98.png

这时我们发现有时候先打印“main…”,有时候会先打印“thread…”

这是由于CPU调度线程是抢占式执行的,这个现象是程序猿无法控制的,完全是由CPU内部的执行机制造成的

线程的执行先后顺序,取决于操作系统、调度器的具体实现

3eee73c82e31ec95e7a598a36e632a09.png

此时我们还看不到Java线程,这时我们需要借助JDK为我们提供的工具jconsole

00231fe7e7acd0118bf52b1651462be4.png

打开jconsole之后,我们就能查看线程的情况了(此时程序要处于运行状态)

7d611e56a51e48e5a07bb37302d97004.png

849abcbd6451bc80b705287a289838eb.pnge11869d3fe91ed30fcf8a50dbaf46742.png

其他线程都是JVM自己创建的线程,例如:JC垃圾回收器

2.3 sleep方法

为了方便观察线程的详细情况,我们适当的让线程"休息"一下,减缓刚刚执行的死循环代码,我们可以用sleep来进行操作

sleep是"休眠"的操作,让线程进入"阻塞"状态,放弃占有CPU时间片,让给其他线程使用

使用方法:Thread.sleep();sleepThread静态成员方法,可直接通过 类名.方法名的方式调用

形参:毫秒

使用时需要使用try...catch...处理中断异常

2.4 run和start方法的区别

作用功能不同:

  1. run方法的作用是描述线程具体要执行的任务;
  2. start方法的作用是真正的去申请系统线程
  3. 运行结果不同:
  4. run方法是一个类中的普通方法,主动调用和调用普通方法一样,会顺序执行一次;
  5. start调用方法后, start方法内部会调用Java 本地方法(封装了对系统底层的调用)真正的启动线程,并执行run方法中的代码,run 方法执行完成后线程进入销毁阶段。

3.创建线程

3.1 继承Thread类

例如上面的代码,创建一个类继承Thread类,重写run方法

public class Demo1 {
    public static void main(String[] args) {
        MyTread myTread=new MyTread();
        //myTread.start();
        myTread.run();
        //在死循环中做打印
        while (true){
            System.out.println("main...");
            try {
                MyTread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
//通过继承Tread类的方式创建一个线程
class MyTread extends Thread{
    @Override
    public void run() {
        while (true){
            System.out.println("thread...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

3.2实现Runnable接口

public class Demo2 {
    public static void main(String[] args) {
        //实例化Runnable
        Runnable myRunnable=new MyRunnable();
        //通过Thread构造方法传入参数myRunnable
        Thread thread=new Thread(myRunnable);
        thread.start();
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("MyRunnable");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Runnable 接口内唯一声明了 run 方法,由 Thread 类实现。使线程与任务分离开,可以更好的解耦合,“高内聚,低耦合”,使用实现Runnable接口的方法更优

让任务与线程分离,以便后面在修改代码时影响较小,就可以不用关注线程创建代码,只关心线程任务中的代码,方便代码的维护

新建相同任务的线程时,就不用重写run方法,直接将定义好的Runnable传入就可以了

3.3 通过匿名内部类创建线程

public class Demo4 {
    public static void main(String[] args) {
        Thread t1=new Thread(){
            @Override
            public void run() {
                while (true){
                    System.out.println("通过创建Thread类的匿名内部类创建线程");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        t1.start();
    }
}

3.4通过实现Runnable接口的匿名内部类的方式创建线程

public class Demo5 {
    public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                int count=0;
                while (true){
                    count++;
                    System.out.println("通过实现Runnable接口的匿名内部类的方式创建线程"+count);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        thread.start();
    }
}

3.5通过Lambda表达式的方式创建线程(推荐使用)

public class Demo6 {
    public static void main(String[] args) {
        Thread thread=new Thread(()->{
            int count =0;
            while (true) {
                count++;
                System.out.println("通过Lambda表达式的方式创建线程 " + count);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        thread.start();
    }
}

4.多线程的优点

增加运行速度

分别使用串行和并行实现10亿次累加

public class Demo7 {
    private static final long count=10_0000_0000L;
    public static void main(String[] args) {
        //串行
        serial();
        //并行
        parallel();
    }
    //并行
    private static void parallel() {
        long begin=System.currentTimeMillis();
        Thread t1=new Thread(()->{
            long a=0L;
            for (int i = 0; i < count; i++) {
                a++;
            }
        });
        Thread t2=new Thread(()->{
            long b=0L;
            for (int i = 0; i < count; i++) {
                b++;
            }
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        long end=System.currentTimeMillis();
        System.out.println("并行耗时:"+(end-begin)+"ms.");
    }
    //串行
    private static void serial() {
        long begin=System.currentTimeMillis();
        long a=0L;
        for (int i = 0; i < count; i++) {
            a++;
        }
        long b=0L;
        for (int i = 0; i < count; i++) {
            b++;
        }
        long end=System.currentTimeMillis();
        System.out.println("串行耗时:"+(end-begin)+"ms.");
    }
}

运行结果:

52a55fb6166f742cb6faeba0be83f684.png

这时我们可以看到并行的耗时明显小于串行的耗时,当让任务量大时,多线程的效率会提高不大,但是当任务量不大时,可能多线程的效率还没有单线程的效率高,毕竟创建线程时CPU的调度也是有开销的。

目录
相关文章
|
6天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
21 1
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
35 3
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
25 2
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
41 2
|
2月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
47 1
|
2月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
55 1
|
2月前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
44 1
|
1月前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
67 0
|
2月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
88 6
|
2月前
|
存储 运维 NoSQL
Redis为什么最开始被设计成单线程而不是多线程
总之,Redis采用单线程设计是基于对系统特性的深刻洞察和权衡的结果。这种设计不仅保持了Redis的高性能,还确保了其代码的简洁性、可维护性以及部署的便捷性,使之成为众多应用场景下的首选数据存储解决方案。
45 1