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

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

前言


本文会介绍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。



相关文章
|
7月前
|
存储 小程序 安全
东郊到家小程序系统开发|源码部署|案例详情
智能合约是基于区块链技术的一种计算机程序
|
存储 供应链 算法
交易所项目系统开发案例详细丨交易所系统开发(源码逻辑)/方案设计/源码程序
  广义来讲,区块链技术是利用块链式数据结构来验证与存储数据、利用分布式节点共识算法来生成和更新数据、利用密码学的方式保证数据传输和访问的安全、利用由自动化脚本代码组成的智能合约来编程和操作数据的一种全新的分布式基础架构与计算方式。
|
安全 区块链
阐述永续合约交易所系统开发方案逻辑及案例项目丨源码程序
阐述永续合约交易所系统开发方案逻辑及案例项目丨源码程序
|
监控 安全 数据挖掘
泰山众筹系统开发详细指南丨设计方案丨规则玩法丨逻辑功能丨步骤需求丨源码程序
泰山众筹系统是一个基于区块链技术的众筹平台,旨在为用户提供一个安全、透明和高效的众筹环境。
交易所系统开发详细项目丨案例规则丨方案设计丨步骤需求丨逻辑功能丨源码程序
Requirement analysis and planning: Collaborate with customers to clarify the requirements and goals of the coin exchange system. Understand the customer's business model, target user group, and currencies to be supported by the exchange, and develop a detailed demand plan.
|
前端开发 JavaScript NoSQL
交易所系统开发详细需求/案例规则/玩法设计/步骤项目/源码教程
The development source code of the exchange system refers to the source code used to build the entire exchange system. Exchange development source code usually includes multiple parts such as front-end, back-end, and database.
|
安全 Go 区块链
区块链游戏链游系统开发功能详情丨方案逻辑丨开发项目丨案例分析丨源码规则
 In recent years, with the continuous development of blockchain technology, NFTs (non homogeneous tokens) and DAPPs (decentralized applications) have emerged in the gaming industry.
|
存储 安全 测试技术
去中心化社交软件ktalk开发步骤详情(源码demo实例分析)
ktalk是一款基于区块链技术的去中心化社交软件,它的设计理念是解锁社交自由,让用户可以畅所欲言,不受言论限制,实现真正的社交自由。
|
人工智能 5G 区块链
DAPP合约燃烧机制项目系统开发详细方案/案例项目/源码程序
  区块链、人工智能、数字孪生、人机交互、物联网等面向数据的新一代信息技术的演进并非偶然,而是从Web2.0向Web3.0演进的技术准备。
|
存储 缓存 算法
藏不住了!一线大厂内部图可视分析的需求清单
藏不住了!一线大厂内部图可视分析的需求清单
149 0
下一篇
无影云桌面