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

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

前言


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



相关文章
|
6月前
|
监控 项目管理
PMBOK泛读(第四章) - 项目整合管理(二)
PMBOK泛读(第四章) - 项目整合管理(二)
36 0
|
8月前
|
项目管理
深入解析PMP项目内部运营环境
在项目管理领域,PMP认证是一项备受尊敬的资格,它强调了对项目内部运营环境的深刻理解。PMP专业人士不仅需要了解项目管理的基本原则,还需要熟悉项目内部运营环境的方方面面。本文将深入探讨PMP项目内部运营环境的重要性以及如何有效管理这一环境。
|
3天前
|
小程序 UED
人力资源小程序的设计与开发步骤
人力资源小程序的设计与开发步骤
25 1
|
3天前
|
JavaScript Java 测试技术
基于Java的大学生成果登记系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的大学生成果登记系统的设计与实现(源码+lw+部署文档+讲解等)
20 0
|
3天前
|
JavaScript Java 测试技术
基于Java的贫困认定管理平台的设计与实现(源码+lw+部署文档+讲解等)
基于Java的贫困认定管理平台的设计与实现(源码+lw+部署文档+讲解等)
26 1
|
6月前
|
自然语言处理 数据挖掘 项目管理
PMBOK泛读(第五章) - 项目范围管理(二)
PMBOK泛读(第五章) - 项目范围管理(二)
38 0
|
6月前
|
数据可视化 数据挖掘 测试技术
PMBOK泛读(第五章) - 项目范围管理(一)
PMBOK泛读(第五章) - 项目范围管理
55 0
|
6月前
|
监控 数据挖掘 项目管理
PMBOK泛读(第四章) - 项目整合管理(三)
PMBOK泛读(第四章) - 项目整合管理(三)
29 0
|
6月前
|
数据挖掘 项目管理 索引
PMBOK泛读(第四章) - 项目整合管理(四)
PMBOK泛读(第四章) - 项目整合管理(四)
31 0
|
7月前
|
监控 安全 数据挖掘
泰山众筹系统开发详细指南丨设计方案丨规则玩法丨逻辑功能丨步骤需求丨源码程序
泰山众筹系统是一个基于区块链技术的众筹平台,旨在为用户提供一个安全、透明和高效的众筹环境。