栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

多线程|创建多线程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程|创建多线程

进程和线程的概念 进程的概念

什么是进程?和程序一样吗?

程序是一系列有组织的文件,封装操作系统的各种API,实现不同的效果和进程

比如QQ这个软件的核心就是聊天,这就封装了发送网络请求、接收网络请求等一系列的API,最后将其变成文件

进程是程序在系统中的一次执行过程

进程是现代操作系统中资源分配(CPU、内存等关键系统资源)的最小单位

之前使用MySQL服务,其实就是启动MySQL的服务端进程

IP地址是在网络上唯一标识一台主机的位置,就相当于我们所在地址,别人通过这个地址就能找到这个地方

通过IP地址能找到主机,那如何定位主机上的某个进程呢?

我们通过端口号在操作系统上唯一地定位一个进程

Http的端口号:80,Https的端口号:443,还有我们写的MySQL服务端启动之后的进程端口号就是3306

进程的端口号一般不变,进程的PCB(操作系统描述本次进程的信息接口)的PID每次启动进程都会改变

线程的概念

我们现代OS都是多线程操作系统,那究竟什么是线程

比如买CPU的时候都有几核心几线程

4核8线程指的是当前电脑的CPU有4个核心(4个独立的芯片),每个核心能并行处理两个线程,所以就能并行执行8个线程

线程是进程中的一个独立任务,或者说是一个子类

在浏览器上可以一边下载视频,一边听音乐,下载视频就是一个线程,听音乐就是另一个线程

不同的进程之间是相互独立的,而同一个进程的所有线程是共享进程的资源的,线程是操作系统任务执行的基本单位

线程和进程的区别
  1. 进程是OS资源分配的基本单位,线程是操作系统中任务执行(也称系统调度)的基本单位
  2. 创建和销毁进程的开销要远比创建和销毁线程大得多(创建和销毁一个进程的时间要比创建和销毁一个线程大得多),线程更加轻量化
  3. 调度一个线程比调度一个进程快得多,比如你启动浏览器和新建一个浏览器的标签速度是由明显差别的
  4. 进程包含线程,每个进程至少包含一个线程(主线程)
  5. 进程之间彼此相对独立,不同的进程不会共享内存空间,而同一个进程的线程共享内存空间

我们在程序中写的main方法就是主线程,java命令 主类名称就是在启动一个Java进程

JDK提供的线程库实际上就是利用操作系统提供的线程库进行二次封装

多线程的使用

以前写代码的入口都是main主方法(主线程),所有的调用都是从主方法开始的,所有的任务都在主方法(主线程)中进行

Java中描述线程这个对象的类是 - java.lang.Thread,该类线程的核心类,都是通过Thread类来启动一个新的类

多线程代码示例:

public class FirstThreadDemo {
    private static class MyThread extends Thread {
        //run方法就是线程的核心工作方法,线程要干的所有事情都在run方法中进行定义
        @Override
        public void run() {
            Random random = new Random();
            while (true) {
                //打印当前线程的名称
                System.out.println(Thread.currentThread().getName());
                //当前线程随机暂停0-9秒
                try {
                    Thread.sleep(random.nextInt(10));
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    
    // main方法就对应一个主线程
    public static void main(String[] args) {
        //创建三个线程对象
        MyThread m1 = new MyThread();
        MyThread m2 = new MyThread();
        MyThread m3 = new MyThread();
        //启动三个子线程
        m1.start();
        m2.start();
        m3.start();
        Random random = new Random();
        while (true) {
            //打印当前线程的名称
            System.out.println(Thread.currentThread().getName());
            //当前线程随机暂停0-9秒
            try {
                Thread.sleep(random.nextInt(10));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

启用线程就调用start方法,线程启动之后会由JVM自动执行每个线程的run方法,每个线程的执行都是并行的

只有主线程的时候,代码的执行顺序就是从主方法第一行,而上面代码中的多了3个子线程,则4个线程就在电脑中同时执行,互不干扰

代码执行起来的结果:

主线程和子线程同时执行的图示:

问题:根据上面代码如果我们让所有线程顺序执行,那么主方法会不会打印输出?其他线程会不会打印输出?

如果是顺序执行,主方法肯定不会打印输出,要等子线程结束之后才能执行后面的代码,但是子线程中是一个死循环,所以除了第一个子线程,其他子线程都不会打印输出

run方法和start方法的区别

run方法是线程类的工作任务 - run方法决定了线程启动之后要干的事情,当run方法执行完毕(run由JVM来执行),线程就进入销毁状态

start方法就Thread类中启动线程的方法,只有当线程对象调用start方法之后才会被系统调度,进入运行状态

如何查看JVM中的线程状态

JDK和JRE相比最大的区别,提供了很多开发程序会用到的辅助工具

我们在终端执行jconsole命令 - 查看当前运行JVM内部的线程情况

前两种创建线程的方式

Java中创建一个线程一共有四种方法:

  1. 继承Thread类,覆写run方法(线程的核心工作任务)
  2. 实现Runnable接口,覆写run方法
  3. 实现Callable接口,覆写call方法
  4. 使用线程池创建线程

第一二种方法,最终启动线程都是通过Thread类的start方法启动线程,至于第三四种方法,不在此模块讲解,会在后面的内容进行介绍

1. 继承Thread类

该方式下的线程创建与启动三步走:

  1. 一个子类继承Thread类
  2. 覆写run方法
  3. 产生当前子类对象,然后调用start方法启动子线程

代码示例:

// ThreadMethod这个子类就是一个线程实体
public class ThreadMethod extends Thread{
    @Override
    public void run() {
        System.out.println("这是子线程的的输出结果");;
    }
}
public class Main {
    public static void main(String[] args) {
        // 创建线程类的对象
        ThreadMethod mt = new ThreadMethod();
        // 启动子线程
        mt.start();
        //执行主线程的语句
        System.out.println("主线程的输出结果");
    }
}

输出结果:

调用start方法启动线程,是由JVM来产生操作系统的线程并启动的,到底什么时候会真正启动,对我们来说是不可见的,也不能控制,因此会出现上面两种情况

2. 实现Runnable接口

该方式的线程创建依然是三步走:

  1. 实现Runnable接口
  2. 覆写run方法
  3. 和第一种方法一样,还是创建Thrad类的对象,调用start方法

代码示例:

public class RunnableMethod implements Runnable {
    @Override
    public void run() {
        System.out.println("这是Runnable方式实现的子线程任务");
    }
}
public class Main {
    public static void main(String[] args) {
        // 创建线程任务的对象
        RunnableMethod runnableMethod = new RunnableMethod();
        // 创建线程对象,将任务对象作为参数传入线程对象的构造方法中
        Thread thread = new Thread(runnableMethod);
        // 启动线程
        thread.start();
        // 执行主线程的语句
        System.out.println("主线程的输出结果");
    }
}

相比第一种,更推荐使用方式2,实现Runnable接口更加灵活,子类还能实现别的接口,继承别的类

而使用方式1的话,就只能继承Thread,存在单继承局限性

3. 关于方式1和方式2的不同写法

1.使用匿名内部类创建Thread对象

public static void main(String[] args) {
    // 匿名内部类继承Thread类
    Thread t1 = new Thread() {
        @Override
        public void run() {
            System.out.println("匿名内部类继承Thread类");
            System.out.println("这是子线程" + Thread.currentThread().getName());
        }
    };
    t1.start();
    System.out.println("这是主线程" + Thread.currentThread().getName());
}
// 并发执行的结果
匿名内部类继承Thread类
这是主线程main
这是子线程Thread-0

2.使用匿名内部类实现Runnable接口

public static void main(String[] args) {
    // 这里即实现了Runnable接口,又进行了传参
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("匿名内部类实现Runnable接口");
            System.out.println("这是子线程" + Thread.currentThread().getName());
        }
    });
    thread.start();
    System.out.println("这是主线程" + Thread.currentThread().getName());
}
// 并发执行的结果
匿名内部类实现Runnable接口
这是子线程Thread-0
这是主线程main

3.使用Lambda表达式

public static void main(String[] args) {
    Thread t1 = new Thread(() -> System.out.println("Lambda表达式实现Runnable接口"));
    t1.start();
    System.out.println("这是主线程" + Thread.currentThread().getName());
}
// 并发执行的结果
这是主线程main
Lambda表达式实现Runnable接口

由于Lambda表达式只能用于函数式接口的实现,所以Thread类就享受不到这种简化写法了

暑期编程PK赛 得CSDN机械键盘等精美礼品!
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1017291.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号