【JavaEE】_线程与多线程的创建

目录

[1. 线程的概念](#1. 线程的概念)

[2. 创建与使用多线程](#2. 创建与使用多线程)

[2.1 方式1:继承Thread类](#2.1 方式1:继承Thread类)

[2.2 方式2: 实现Runnable接口](#2.2 方式2: 实现Runnable接口)

[2.3 以上两种创建线程方式的对比](#2.3 以上两种创建线程方式的对比)

[3. 多线程的优势-增加运行速度](#3. 多线程的优势-增加运行速度)


1. 线程的概念

进程的存在是由于系统的多任务执行需求,这也要求程序员进行并发编程;

使用多进程是完全可以实现并发编程的,但如果要频繁地创建或销毁(如分配、销毁内存或文件)以及频繁地调度进程,资源的申请和释放不仅低效,成本也非常高;

为了解决这个问题,通常会通过两个方式:

(1)进程池:效率有一定提高,但进程池中的闲置进程不使用的时候仍然在消耗系统资源,故而使用进程池的系统资源消耗是非常大的;

(2)线程:线程比进程更轻量,每个线程也能够执行一个任务(代码),也能够并发编程;

创建、调度、销毁一个线程的成本相比进程而言要低很多,在Linux上也把线程称为轻量级进程,

进程重量重在资源的申请和释放,线程则是包含在进程中的,一个进程中的多个线程共用同一份资源(同一份内存+文件),只有在创建进程的第一个线程时,由于需要分配资源,成本是相对较高对的,后续在这个进程中再创建其他线程的成本都比较低

但是并非线程越多越好,如果线程过多,就会存在资源竞争导致速度受限;

注:进程与线程的区别与联系?

(1)进程包含线程,一个进程里可以包含一个线程,也可以包含多个线程;

(2)进程和线程都是为了处理并发编程场景,但进程频繁创建、调度、释放时效率较低,消耗较大;而线程由于少了申请释放资源的过程,故而更轻量,创建、调度、释放都效率更高,消耗更少

(3)操作系统创建进程需要给进程分配资源,故而进程是操作系统分配资源的基本单位

操作系统创建线程是要在CPU上调度执行,故而线程是操作系统调度执行的基本单位

(4)进程具有独立性,每个进程都由各自的虚拟地址空间,进程之间互不影响;

同一个进程中的多个线程共用同一个内存空间,线程之间可能会互相影响;

2. 创建与使用多线程

2.1 方式1:继承Thread类

java标准库提供了一个Thread类来表示、操作线程,Thread类也可视为是java标准库提供的API;

创建好的Thread实例和操作系统中的线程是一一对应的关系;

操作系统提供了一组关于API(C语言),java对于这组API进一步封装形成了Thread类;

示例代码1:单线程创建示例

java 复制代码
class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println("Hello Thread.");
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
    }
}

注:(1)通过Thread类创建线程有很多种写法,最简单的就是创建子类继承Thread并且重写run方法;

(2)run方法中描述该线程要执行哪些代码,由于每个线程都是并发执行的,因此需要告知每个线程要执行的代码内容,run方法中的逻辑是在新创建出的线程中被执行的代码;

(3)start方法的调用代表着在系统中真正创建了线程,此时才开始执行上文的run操作;

(4)这里创建线程是在同一个进程中创建的;

(5)线程之间是并发进行的:

(6)线程强制中断异常是多线程中最常遇到的异常之一:

(7)Thread是java.lang中的类,是不需要导入包的,类似的还有String也是不需要导入的;

示例代码2:多线程创建示例

java 复制代码
class MyThread extends Thread{
    @Override
    public void run(){
        while(true){
            System.out.println("Hello Thread");
            try {
                Thread.sleep(1000);
                //休眠:强制使线程进入阻塞状态  单位为ms
                //即1s内这个线程不会到cpu上执行

            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
        while(true){
            System.out.println("Hello Main");
            try {
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

截取部分输出结果如下:

注:(1)一个进程中至少会有一个线程,在一个java进程中也至少会有一个调用main方法的线程,只是该线程是系统自动生成的而非手动创建的,此时我们手动创建的t线程与自动创建的main线程就是并发执行的关系,这两个线程从宏观上看该输出结果就是同时执行的;

(2)两个线程都是打印一条语句后休眠1s,当1s结束后,系统先唤醒哪个线程是随机的,即对于操作系统来说,内部对线程之间的调度顺序在宏观上也可以认为是随机的,这种调度方式也称为抢占式执行;

(3)sleep是一个毫秒级休眠语句,并没有那么精确,比如sleep(1000)的含义是1000ms之内不能上CPU,而不是1000ms之后准时上CPU,故而结束阻塞状态的具体时间是不确定的,这与线程之间的调度是随机的也是彼此互相印证的;

(4)start方法用于启动线程;

示例代码3:使用匿名内部类

java 复制代码
public class Demo3 {
    public static void main(String[] args) {
        //1.创建一个匿名内部类继承自Thread类
        //2.重写run方法
        //3.new这个匿名内部类的实例
        Thread t = new Thread(){
            public void run(){
                System.out.println("Hello Thread.");
            }
        };
        t.start();
    }
}

2.2 方式2: 实现Runnable接口

创建一个类实现Runnable接口,再创建Runnable实例传给Thread实例;

代码示例1:实现Runnable接口创建线程

java 复制代码
//Runnable 就是在描述一个任务
class MyRunnable implements Runnable{
    @Override
    public void run(){
        System.out.println("Hello");
    }
}
public class Demo2 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
    }
}

通过Runnable来描述任务的内容,进一步地再把描述好的任务交给Thread实例;

代码示例2:使用匿名内部类

java 复制代码
public class Demo4 {
    public static void main(String[] args) {
        //1.new Runnable的匿名内部类
        //2.将new的Runnable实例传给Thread的构造方法
        Thread t = new Thread(new Runnable(){
            public void run(){
                System.out.println("Hello Thread.");
            }
        });
        t.start();
    }
}

需要将new 的Runnable的实例传递给Thread,故而需要包含其重写的run方法;

代码示例3:使用lambda表达式

java 复制代码
public class Demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println("Hello Thread.");
        });
        t.start();
    }
}

lambda表达式就是一个匿名方法,()表示方法参数,->表示是一个lambda,{}中编写方法内容;

2.3 以上两种创建线程方式的对比

通常认为Runnable方式会更好一点,能够做到让线程与线程执行的任务更好地进行解耦;

编写代码通常希望高内聚、低耦合。

Runnable只是描述了一个任务,但是任务的执行方式是进程、线程、线程池还是协程来执行,Runnable内部并不作涉及,Runnable内部的代码也不涉及;

3. 多线程的优势-增加运行速度

多线程编程的优势显著体现在可以提高任务完成的效率:

如现有两个整数变量,分别对这两个变量自增10亿次,分别使用一个线程与两个线程进行演示:

java 复制代码
public class Demo6 {
    private static final long count = 10_0000_0000;

    public static void serial(){  //串型执行
        //记录程序执行时间
        long beg = System.currentTimeMillis();
        long a = 0;
        for(long i=0;i<count;i++){
            a++;
        }
        long b = 0;
        for(long i=0;i<count;i++){
            b++;
        }
        long end = System.currentTimeMillis();
        System.out.println("Single Thread Time: "+(end-beg)+" ms.");
    }
    public static void concurrency() throws InterruptedException {
        long beg = System.currentTimeMillis();
        Thread t1 = new Thread(()->{
            long a = 0;
            for(int i=0;i<count;i++){
                a++;
            }
        });
        t1.start();
        Thread t2 = new Thread(()->{
            long b = 0;
            for(int i=0;i<count;i++){
                b++;
            }
        });
        t2.start();
        //不能在此处直接记录结束时间,该方法是在main线程中执行的;
        //main线程与t1、t2线程是并发执行的,即t1、t2尚未执行结束此时就已经记录结束时间了
        //应将main线程等待t1与t2线程执行完毕再进行计时
        t1.join();
        t2.join();
        //join方法效果就是等待线程结束,哪个线程调用则令main线程等待哪个线程结束
        long end = System.currentTimeMillis();
        System.out.println("Multi Thread Time: "+(end-beg)+" ms.");
    }
    public static void main(String[] args) throws InterruptedException {
        serial();
        concurrency();
    }
}

输出结果为:

注:(1)增加线程并非一定会达到翻倍的速度提升,因为两个线程在底层到底是并行执行还是并发执行并不确定, 底层微观真正并行执行的时候,效率才会有显著提升;

(2)当count不够大时,反而可能会导致程序执行速度更慢,因为创建线程本身也需要时间开销,此时代码的执行时间反而更多地消耗在了创建线程上;

(3)多线程适合应用于CPU密集型的程序,当程序需要进行大量的计算时,使用多线程就可以更充分地利用CPU的多核资源;

相关推荐
七星静香10 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员11 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU12 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie615 分钟前
在IDEA中使用Git
java·git
Elaine20239130 分钟前
06 网络编程基础
java·网络
G丶AEOM32 分钟前
分布式——BASE理论
java·分布式·八股
落落鱼201332 分钟前
tp接口 入口文件 500 错误原因
java·开发语言
想要打 Acm 的小周同学呀33 分钟前
LRU缓存算法
java·算法·缓存
镰刀出海36 分钟前
Recyclerview缓存原理
java·开发语言·缓存·recyclerview·android面试
阿伟*rui3 小时前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel