Java之多线程(一)—技术简介

1、线程的“母亲“——进程

        进程(process):是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

        例如上图所示,当通过windows打开任务管理时,你所有正在使用的软件都会对应一个进程。无论是看不到的,还是看的到的程序,都会有一个进程。

2、什么是线程

        现在操纵系统在运行一个程序时,比如:在Windows中看到后缀位.exe的文件,就是一个程序。不过程序是死的,静态的。当你双击这个带有.exe的程序图标时,这个.exe文件中的指令就会被加载,那么操作系统就会为这个.exe程序,创建一个进程。进程是“活”的,是正在被执行的指令。而具体执行的员工则为线程

        线程则是轻量级的进程,是程序执行的最小单位。使用多线程而不使用多进程去进行并发程序设计,是因为线程间的切换和调度成本远远小于进程。

        同样的在任务管理器中点击性能一栏中的CPU,会发现我们现在使用的电脑就是一个多线程的环境

 在上图中明确表明了,当前运行的进程和线程数量,除此之外还有句柄的数量(标识符,用于记录对象在内存中的地址,实际上就是指向指针的指针,指向对应的内存地址),内核数量,L1、L2和L3缓存的大小(这个跟后面的JMM模型有关系,内存可见性)。

3、并发和并行

        介绍过了进程和线程,不得不说一说在操作系统中,经典的并发和并行的区别。

1、并发(Concurrent):指两个或多个事件在同一时间间隔内发生,即交替做不同事的能力,多线程是并发的一种形式。例如垃圾回收时,用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。

2、并行(Parallel):指两个或者多个事件在同一时刻发生,即同时做不同事的能力。例如垃圾回收时,多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。

4、天生的多线程程序:Java 程序

        一个java程序从main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上java程序天生就是多线程程序。因为执行main()方法的是一个名为main的线程。下面是用JMX来查看一个普通的java程序包含哪些线程。如下图所示:

package com.copy.thread;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

public class MultiThread {

    public static void main(String[] args) {

        // 获取java线程管理 MXBean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        //遍历线程信息
        for(ThreadInfo threadInfo: threadInfos){
            System.out.println("["+threadInfo.getThreadId()+"]"+threadInfo.getThreadName());
        }


    }
}

结果:

[6]Monitor Ctrl-Break // IDEA run模式自带的线程
[5]Attach Listener  // 请求接收线程
[4]Signal Dispatcher  // 分发处理发送给JVM信号的线程
[3]Finalizer  // 调用对象finalize方法的线程,垃圾回收
[2]Reference Handler  // 清除reference的线程
[1]main  //main线程 用户程序入口

其中4和5重点介绍一下

AttachListener(请求接收线程)

       AttachListener线程是负责接收到外部的命令,而对该命令进行执行的并且把结果返回给发送者。通常我们会用一些命令去要求JVM给我们一些反馈信息,如:java  -version、jmap、jstack等等。如果该线程在JVM启动的时候没有初始化,那么,则会在用户第一次执行JVM命令时,得到启动。

Signal Dispatcher(信号转发线程)

      前面我们提到第一个Attach Listener线程的职责是接收外部JVM命令,当命令接收成功后,会交给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。signal dispather线程也是在第一次接收外部JVM命令时,进行初始化工作。

区别:

      可以看出以上的AttachListener线程和Signal Dispatcher线程是属于两者相互解耦的线程,有点类似单一职责且加入了命令模式的概念在里面,一个抓门负责接受(AttachListener)之后进行封装后转发给执行线程(Signal DIspatcher)统一进行执行和派遣工作。如同邮局收件和运件属于两个部门。

5、多线程的好处

1、可以使用更多的处理器核心

        随着处理器的核心数量越来越多,多线程技术得到了广泛的应用。当程序是用多线程技术时,可以将计算逻辑分配到多个处理器核心上,就会显著减少程序的处理时间,并且随着处理器核心的加入而变得更有效率。

2、更快的响应时间

        有时我们会编写较为复杂的代码(这里的复杂不是指算法的复杂,而是复杂的业务逻辑),例如:一笔订单的创建,涉及到:插入订单数据,生成订单快照,扣减订单库存,发送短信通知买家和卖家记录货品的销售数量。用户从点击下单开始,要等待这些操作全部完成,才能看到订购成功的结果。但是这么多业务操作,如何能够让其更快的完成呢?

        在上面的场景中,可以使用多线程技术,即将数据一致性不强的操作派发给其他线程处理(也可以使用消息中间件),如生成订单快照,发送短信等。这样做的好处是,响应用户请求的线程尽可能快的处理完成,缩短了响应时间,提升了用户体验。

3、更好的编程模型

Java为多线程提供了良好、考究并且一致的编程模型(JMM),使开发人员能够更加专注于问题的解决。

6、多线程的三大特性

        多线程原子性、可见性、有序性

1、原子性

        原子性指的就是一个操作是不可中断,即使有多个线程执行,一个操作开始也不会受其他线程影响,即可以理解为线程的最小执行单元,不可被分割。当然这个最小执行单元可以只是一个操作也可以是一段代码。在线程中实现原子性的操作可以为 synchronized修饰或通过lock( ReenTrantLock)实现

2、可见性

        可见性跟CPU的L1、L2、L3级缓存有关。缓存中有变量时,如果不加限制,CPU直接从当前最近的缓存读取变量值,不会去主存(内存)中读,所以多核并发执行的线程之间可能同一个变量同一时间有不同的值(初始时刻缓存中没有的变量会从内存中读取,然后一级级的保存到缓存中,下次CPU使用时会从最近的缓存中读取该变量)

        解决方式:通过 volatile关键字、 synchronized或通过lock( ReenTrantLock)实现均可实现变量的可见性,但其原理是不一样的,volatile是通过内存屏障实现,而其他两个是通过加锁保证了同一时刻只有一个线程访问资源,即是串行操作。

3、有序性(参考java并发编程艺术)

        有序性即是程序按一定规则进行顺序的执行,期间会进行编译器优化重排、指令重排、内存重排等,执行规则遵循as-if-serial语义规则和happens-before 原则,其中
1) as-if-serial语义规则:即是不管怎么重排,最后的执行结果是不能改变的;
2) happens-before规则:
        ●程序顺序原则,即在一个线程内必须保证语义串行性,也就是说按照代码顺序执行。
        ●锁规则 解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前,也就是说,如果对于一个锁解锁后,再加锁,那么加锁的动作必须在解锁动作之后(同一个锁)。
        ●volatile规则 volatile变量的写,先发生于读,这保证了volatile变量的可见性,简单的理解就是,volatile变量在每次被线程访问时,都强迫从主内存中读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时刻,不同的线程总是能够看到该变量的最新值。
        ●线程启动规则 线程的start()方法先于它的每一个动作,即如果线程A在执行线程B的start方法之前修改了共享变量的值,那么当线程B执行start方法时,线程A对共享变量的修改对线程B可见
        ●传递性 A先于B ,B先于C 那么A必然先于C
        ●线程终止规则 线程的所有操作先于线程的终结,Thread.join()方法的作用是等待当前执行的线程终止。假设在线程B终止之前,修改了共享变量,线程A从线程B的join方法成功返回后,线程B对共享变量的修改将对线程A可见。
        ●线程中断规则 对线程 interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测线程是否中断。
        ●对象终结规则对象的构造函数执行,结束先于finalize()方法
在满足上面规则的基础上,会做相应的重排优化,针对于多线程,可能会出现结果不一致的情况,这时候可通过volatile禁止相应代码的指令重排, volatile的原理即是通过 内存屏障(一是保证特定程序的执行顺序;二是强制刷出cpu缓存数据,实现数据的可见性)实现禁止指令重排。

详细可以看下面的博客很详细:

线程的三大特性(可见性、有序性、原子性)_skye_fly的博客-CSDN博客_线程三大特性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值