多线程基础体系知识清单(上)

简介: 多线程基础体系知识清单(上)

前言


本文会介绍Java中多线程与并发的基础,适合初学者食用。


线程与进程的区别


在计算机发展初期,每台计算机是串行地执行任务的,如果碰上需要IO的地方,还需要等待长时间的用户IO,后来经过一段时间有了批处理计算机,其可以批量串行地处理用户指令,但本质还是串行,还是不能并发执行。


如何解决并发执行的问题呢?于是引入了进程的概念,每个进程独占一份内存空间,进程是内存分配的最小单位,相互间运行互不干扰且可以相互切换,现在我们所看到的多个进程“同时"在运行,实际上是进程高速切换的效果。


那么有了线程之后,我们的计算机系统看似已经很完美了,为什么还要进入线程呢?如果一个进程有多个子任务,往往一个进程需要逐个去执行这些子任务,但往往这些子任务是不相互依赖的,可以并发执行,所以需要CPU进行更细粒度的切换。所以就引入了线程的概念,线程隶属于某一个进程,它共享进程的内存资源,相互间切换更快速。


进程与线程的区别:


  1. 进程是资源分配的最小单位,线程是CPU调度的最小单位。所有与进程相关的资源,均被记录在PCB中。
  2. 线程隶属于某一个进程,共享所属进程的资源。线程只由堆栈寄存器、程序计数器和TCB构成。
  3. 进程可以看作独立的应用,线程不能看作独立的应用。
  4. 进程有独立的地址空间,相互不影响,而线程只是进程的不同执行路径,如果线程挂了,进程也就挂了。所以多进程的程序比多线程程序健壮,但是切换消耗资源多。


Java中进程与线程的关系:


  1. 运行一个程序会产生一个进程,进程至少包含一个线程。
  2. 每个进程对应一个JVM实例,多个线程共享JVM中的堆。
  3. Java采用单线程编程模型,程序会自动创建主线程 。
  4. 主线程可以创建子线程,原则上要后于子线程完成执行。

线程的start方法和run方法的区别


区别


Java中创建线程的方式有两种,不管使用继承Thread的方式还是实现Runnable接口的方式,都需要重写run方法。调用start方法会创建一个新的线程并启动,run方法只是启动线程后的回调函数,如果调用run方法,那么执行run方法的线程不会是新创建的线程,而如果使用start方法,那么执行run方法的线程就是我们刚刚启动的那个线程。


程序验证


public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new SubThread());
        thread.run();
        thread.start();
    }
}
class SubThread implements Runnable{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("执行本方法的线程:"+Thread.currentThread().getName());
    }
}


image.png


Thread和Runnable的关系


Thread源码


image.png


区别


通过上述源码图,不难看出,Thread是一个类,而Runnable是一个接口,Runnable接口中只有一个没有实现的run方法,可以得知,Runnable并不能独立开启一个线程,而是依赖Thread类去创建线程,执行自己的run方法,去执行相应的业务逻辑,才能让这个类具备多线程的特性。


使用继承Thread方式和实现Runable接口方式分别创建子线程


使用继承Thread类方式创建子线程


public class Main extends Thread{
    public static void main(String[] args) {
        Main main = new Main();
        main.start();
    }
    @Override
    public void run() {
        System.out.println("通过继承Thread接口方式创建子线程成功,当前线程名:"+Thread.currentThread().getName());
    }
}


运行结果:


image.png


使用实现Runnable接口方式创建子线程


public class Main{
    public static void main(String[] args) {
        SubThread subThread = new SubThread();
        Thread thread = new Thread(subThread);
        thread.start();
    }
}
class SubThread implements Runnable{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("通过实现Runnable接口创建子线程成功,当前线程名:"+Thread.currentThread().getName());
    }
}


运行结果:


image.png


使用匿名内部类方式创建子线程


public class Main{
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("使用匿名内部类方式创建线程成功,当前线程名:"+Thread.currentThread().getName());
            }
        });
        thread.start();
    }
}


运行结果:


image.png


关系


  1. Thread是实现了Runnable接口的类,使得run支持多线程。


  1. 因类的单一继承原则,推荐使用Runnable接口,可以使程序更加灵活。

如何实现处理多线程的返回值


通过刚才的学习,我们知道多线程的逻辑需要放到run方法中去执行,而run方法是没有返回值的,那么遇到需要返回值的状况就不好解决,那么如何实现子线程返回值呢?


主线程等待法


通过让主线程等待,直到子线程运行完毕为止。


实现方式:


public class Main{
    static String str;
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                str="子线程执行完毕";
            }
        });
        thread.start();
        //如果子线程还未对str进行赋值,则一直轮转
        while(str==null) {}
        System.out.println(str);
    }
}


使用Thread中的join()方法


join()方法可以阻塞当前线程以等待子线程处理完毕。


实现方式:


public class Main{
    static String str;
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                str="子线程执行完毕";
            }
        });
        thread.start();
        //如果子线程还未对str进行赋值,则一直轮转
        try {
            thread.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(str);
    }
}


join方法能做到比主线程等待法更精准的控制,但是join方法的控制粒度并不够细。比如,我需要控制子线程将字符串赋一个特定的值时,再执行主线程,这种操作join方法是没有办法做到的。


通过Callable接口实现:通过FutureTask或者线程池获取


在JDK1.5之前,线程是没有返回值的,通常程序猿需要获取子线程返回值颇费周折,现在Java有了自己的返回值线程,即实现了Callable接口的线程,执行了实现Callable接口的线程之后,可以获得一个Future对象,在该对象上调用一个get方法,就可以执行子线程的逻辑并获取返回的Object。



相关文章
|
11月前
|
存储 分布式计算 Hadoop
MPP 架构与 Hadoop 架构技术选型指南
MPP架构与Hadoop架构是处理海量数据的两大选择。MPP通过大规模并行处理实现快速查询响应,适用于企业级数据仓库和OLAP应用;Hadoop则以分布式存储和计算为核心,擅长处理非结构化数据和大数据分析。两者各有优劣,MPP适合结构化数据和高性能需求场景,而Hadoop在扩展性和容错性上表现更佳。选择时需综合考虑业务需求、预算和技术能力。
1212 14
|
7月前
|
jenkins 持续交付 开发工具
利用Dockerfile自主构建Jenkins镜像
希望这个过程能善用你的野马般想象,把自己置身于和计算机的卓尔不凡的对话中,让编程的过程充满趣味。
258 36
|
11月前
|
开发框架 前端开发 .NET
一个适用于 .NET 的开源整洁架构项目模板
一个适用于 .NET 的开源整洁架构项目模板
210 26
|
12月前
|
JSON 数据格式
.net HTTP请求类封装
`HttpRequestHelper` 是一个用于简化 HTTP 请求的辅助类,支持发送 GET 和 POST 请求。它使用 `HttpClient` 发起请求,并通过 `Newtonsoft.Json` 处理 JSON 数据。示例展示了如何使用该类发送请求并处理响应。注意事项包括:简单的错误处理、需安装 `Newtonsoft.Json` 依赖,以及建议重用 `HttpClient` 实例以优化性能。
310 2
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
292 2
|
12月前
|
运维 测试技术 持续交付
代码管理的艺术:你的团队是否还在为 Git 分支管理头疼?
本文回顾了作者从2~3人初创团队到百人技术团队的经历,分享了代码管理工具从无到SVN再到Git的演变。重点介绍了Git Flow和GitHub Flow两种常用的Git分支管理模型,分析了它们的适用场景和优缺点。Git Flow适合中大型项目,而GitHub Flow则更适合小型团队和Web应用开发。
361 0
|
消息中间件 存储 安全
微服务之间的数据依赖问题是怎样的?
微服务之间的数据依赖问题是怎样的?
503 0
微服务之间的数据依赖问题是怎样的?
|
移动开发 JavaScript 小程序
uView List 列表
uView List 列表
423 0
|
设计模式 Java 应用服务中间件
掌握这30个设计模式真实案例,挑战年薪60W不是梦
Design Patterns: Elements of Reusable Object-Oriented Software(以下简称《设计模式》),一书由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides合著(Addison-Wesley,1995)。这四位作者常被称为“四人组(Gang of Four)”,而这本书也就被称为“四人组(或 GoF)”书。他们首次给我们总结出一套软件开发可以反复使用的经验,帮助我们提高代码的可重用性、系统的可维护性等,解决软件开发中的复杂问题。
534 3
|
开发框架 JSON 前端开发
Asp.Net Core遇到Swagger(一)-Swashbuckle基础篇
Asp.Net Core遇到Swagger(一)-Swashbuckle基础篇
373 0
Asp.Net Core遇到Swagger(一)-Swashbuckle基础篇

热门文章

最新文章