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

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

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...");
    }
}

image-20230126002310593

重写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...");
    }
}

此时运行结果是

image-20230126002951090

代码先执行的是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);让线程休息一会

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

image-20230126003503919

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

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

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

我们可以在任务管理器中查看Java进程的情况(需要执行死循环操作)

image-20230126010414608

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

image-20230126011115066

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

image-20230126011446916

image-20230126011505393

image-20230126012227493

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

2.3 sleep方法

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

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

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

形参:毫秒

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

2.4 run和start方法的区别

作用功能不同:

  1. run方法的作用是描述线程具体要执行的任务;
  2. start方法的作用是真正的去申请系统线程

运行结果不同:

  1. run方法是一个类中的普通方法,主动调用和调用普通方法一样,会顺序执行一次;
  2. 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.");
    }
}

运行结果:

image-20230127002628511

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

觉得不错就给个三连吧

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小 王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值