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

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

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

文章目录

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的调度也是有开销的。

目录
相关文章
|
20天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
1月前
|
存储 缓存 NoSQL
Redis单线程已经很快了6.0引入多线程
Redis单线程已经很快了6.0引入多线程
31 3
|
1月前
|
消息中间件 安全 Linux
线程同步与IPC:单进程多线程环境下的选择与权衡
线程同步与IPC:单进程多线程环境下的选择与权衡
64 0
|
5天前
|
安全 算法 Java
JavaSE&多线程&线程池
JavaSE&多线程&线程池
126 7
|
6天前
|
存储 缓存 NoSQL
为什么Redis使用单线程 性能会优于多线程?
在计算机领域,性能一直都是一个关键的话题。无论是应用开发还是系统优化,我们都需要关注如何在有限的资源下,实现最大程度的性能提升。Redis,作为一款高性能的开源内存数据库,因其出色的单线程性能而备受瞩目。那么,为什么Redis使用单线程性能会优于多线程呢?
19 1
|
28天前
|
安全 Java 容器
Java并发编程:实现高效、线程安全的多线程应用
综上所述,Java并发编程需要注意线程安全、可见性、性能等方面的问题。合理使用线程池、同步机制、并发容器等工具,可以实现高效且线程安全的多线程应用。
14 1
|
28天前
|
JavaScript 前端开发
JS 单线程还是多线程,如何显示异步操作
JS 单线程还是多线程,如何显示异步操作
22 2
|
1月前
|
消息中间件 Java 数据库连接
【C++ 多线程】C++ 多线程环境下的资源管理:深入理解与应用
【C++ 多线程】C++ 多线程环境下的资源管理:深入理解与应用
38 1
|
1月前
|
Java API C++
【C++ 与Qt 线程】C++ std::thread 与Qt qthread多线程混合编程
【C++ 与Qt 线程】C++ std::thread 与Qt qthread多线程混合编程
48 1
|
1月前
|
消息中间件 算法 开发者
【Qt面试题】多线程情况下, Qt中的信号槽分别在什么线程中执行, 如何控制?
【Qt面试题】多线程情况下, Qt中的信号槽分别在什么线程中执行, 如何控制?
21 1