• Java并发基础--多线程基础


    一、多线程基础知识

    1.进程和线程

    进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

    线程:进程内部的一个独立执行单元;一个进程可以同时并发的运行多个线程,可以理解为一个进程便相当于一个单 CPU 操作系统,而线程便是这个系统中运行的多个任务。

    进程和线程的区别:

    • 进程有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。线程堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。
    • 进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位。
    • 程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行,线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
    • 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点
    • 多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间

    注意:

    • 因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。
    • Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。
    • 创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。

    2.并行和并发

    并行:指两个或多个时间在同一时刻发生(同时发生)
    并发:指两个或多个事件在一个时间段内发生。

    注意:单核处理器的计算机肯定不能并行的处理多个任务,只能是多个任务交替的在单个 CPU 上运行。

    3.线程状态

    • 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
    • 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的成为“运行”。
    • 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得cpu 时间片后变为运行中状态(running)。
    • 阻塞(BLOCKED):表线程阻塞于锁。
    • 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
    • 超时等待(TIME_WAITING):该状态不同于WAITING,它可以在指定的时间内自行返回。
    • 终止(TERMINATED):表示该线程已经执行完毕。

    4.守护线程和非守护线程

    守护线程:和主线程一起结束的线程,叫守护线程,例如典型的守护线程---(GC)垃圾回收线程。
    非守护线程:主线程的结束不影响线程的执行的线程,也叫用户线程。例如用户线程。

    注意:

    • 用户可以将非守护线程设置为守护线程,利用Thread.setDaemon()即可。thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程
    • 在守护线程中产生的新线程也是守护线程
    • 不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。

    5.多线程和单线程的选择

    对于单核CPU,如果是CPU密集型任务,如解压文件,多线程的性能反而不如单线程性能,因为解压文件需要一直占用CPU资源,如果采用多线程,线程切换导致的开销反而会让性能下降。但是对于比如交互类型的任务,肯定是需要使用多线程的、而对于多核CPU,对于解压文件来说,多线程肯定优于单线程,因为多个线程能够更加充分利用每个核的资源。虽然多线程能够提升程序性能,但是相对于单线程来说,它的编程要复杂地多,要考虑线程安全问题。因此,在实际编程过程中,要根据实际情况具体选择。

    6.上下文切换

    对于单核CPU来说,同一个时刻,CPU只能运行一个线程,当运行一个线程过程中发生线程切换,去运行另外一个线程。这个过程即线程的上下文切换,进程的上下文切换类似。注意:对于线程的上下文切换实际上就是存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。多线程可以提高程序的运行效率,但是线程的创建和销毁,以及线程的上下文切换非常的消耗资源,所以有时候多线程未必能带给我们想要的效果。

    7.同步与异步

    同步:单任务模式,排队执行,在一个操作结束前,另外一个操作需要等待。
    异步:多任务模式,多个任务切换占用CPU时间片,切换执行,多线程是异步的。线程被调用的时机是随机的。

    二、多线程--java线程的创建

    java中创建线程的话,一般有两种方式:1)继承Thread类;2)实现Runnable接口。

    1.继承Thread类

     1 /**      
     2 *    继承Thread类,需要重写 Thread 类的 run()方法   
     3 */
     4 public class ThreadDemo extends Thread{
     5     
     6     @Override
     7     public void run() {
     8         System.out.println("子线程执行完毕");
     9     }
    10     public static void main(String[] args) {
    11         ThreadDemo t1 = new ThreadDemo(); 
    12         t1.start();//启动线程,调用run
    13         System.out.println("主线程执行完毕");
    14     }
    15 
    16 }

    结果输出:

    主线程执行完毕
    子线程执行完毕
    或者
    子线程执行完毕
    主线程执行完毕

    通过继承Thread类创建线程类后,通过start()方法启动线程,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。

    2.实现Runnable接口

     1 public class RunableDemo implements Runnable{
     2 
     3     @Override
     4     public void run() {
     5         System.out.println("子线程执行完毕1");
     6     }
     7     
     8     public static void main(String[] args) {
     9         new Thread(new RunableDemo()).start();
    10         System.out.println("主线程执行完毕1");
    11     }
    12 
    13 }

    结果输出:

    子线程执行完毕1
    主线程执行完毕1
    或
    主线程执行完毕1
    子线程执行完毕1

    通过实现Runnable接口,我们定义了一个子任务,然后将子任务交由Thread去执行。注意,这种方式必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,是不会创建新线程的,与普通的方法调用没有任何区别。

    3.优先选择通过实现接口的方式来实现多线程的原因

    • 可以避免由于Java的单继承特性而带来的局限;
    • 增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;
    • 适合多个相同程序代码的线程区处理同一资源的情况。
  • 相关阅读:
    DevOps实施方法论,为什么大企业一定要使用DevOps?
    SpringCloudAlibaba基础入门,基于Nacos构建分布式与配置,Sentinel服务治理
    艾编程Java进阶架构师必看:15次架构演进详解
    实战笔记:来一起探究下Kafka是如何实现万亿级海量数据的高并发写入的?
    520疯狂之后我彻底蒙了,老板让我做技术选型,数据处理选kafka还是RocketMQ?
    如何实现Redis数据持久化以及内存管理之缓存过期机制
    SpringBoot源码深度解析
    分布式缓存Redis高级应用实战:为什么要用缓存机制
    全面上云实战教程:基于阿里云安装配置部署docker详解
    Solr学习笔记(2)—— solr-7.0.0 安装与目录说明
  • 原文地址:https://www.cnblogs.com/liupiao/p/9318528.html
Copyright © 2020-2023  润新知