唐山滦县网站建设wordpress如何做淘宝客

当前位置: 首页 > news >正文

唐山滦县网站建设,wordpress如何做淘宝客,为什么打不开建设银行网站,wordpress桌面应用本文仅是本人对问题的思考记录#xff0c;并没有实操验证#xff0c;有误请大家评论指出。 今天见到了一个经典的问题#xff0c;单核CPU是否有线程可见性问题#xff0c;学完操作系统应该可以直接回答#xff0c;不会有线程安全问题。但如果结合JVM虚拟机来进行分析并没有实操验证有误请大家评论指出。 今天见到了一个经典的问题单核CPU是否有线程可见性问题学完操作系统应该可以直接回答不会有线程安全问题。但如果结合JVM虚拟机来进行分析就又麻烦了一点。结合线程切换和JVM内存区域本文重新梳理了一下对这个问题的思考。 文中可能还有其他问题例如JDK8之后就没有方法区的概念了 以及最后的data1只是为了表示对数据进行更改没有考虑变量存储位置和引用类型等问题。 1 从JVM内存区域说起 我们都知道JVM的内存区域有两部分一部分是线程共享的堆和方法区JDK8之后变为元空间的概念且位于本地内存中另一部分是线程私有的虚拟机栈、本地方法栈、程序计数器。 既然是线程私有的那么如果有多线程难道有多个虚拟机栈、本地方法栈、程序计数器吗答案是否定的。一个JVM虚拟机的内存区域就是这样的。那么问题来了一个线程占有了这三个私有区那其他线程到哪去了我们需要回顾操作系统中的线程和进程间的关系 线程和进程的关系 王道书里是这么解释的进程是一个独立的运行单位也是操作系统进行资源分配的基本单位。而线程不拥有资源但却是CPU调度的最小单位。 进程。它包含三个部分PCBProcess Control Block进程控制块、程序段、数据段。其中PCB为核心该结构常驻内存与进程同生共死。切换进程的时候处理机状态信息必须保存到响应的PCB中以便在该进程重新执行时能从断点继续执行。 线程。除了有TCBThread Control Block线程控制块之外还有其专有的存储区。切换线程的时候状态信息需要保存到TCB中。 这里插入思考一个问题为什么线程切换比进程切换更轻量级 回答这个问题我们首先要知道“保存状态信息”到底保存了什么由于一个进程用到的数据都会加载到内存中可能只加载了一部分通过虚拟内存所以PCB中保存的都是一些地址引用而不是数据实体。大概包括 进程描述信息PID、UID进程控制和管理信息进程状态、优先级、代码入口地址、CPU占用时间、程序的外存地址等资源分配清单I/O设备信息、代码段指针、数据段指针、堆栈指针、文件描述符等CPU相关信息各种寄存器的值、状态字 即便是只需要保存地址引用但仍然需要保存这么多的信息。而线程切换时TCB需要保存的相比之下就轻量级很多 PC程序计数器寄存器、栈等信息。 这两个一对比就发现了切换的代价线程切换明显更轻。 线程与进程在Android中的表现 一个Java程序通过main()开启了它的进程生涯承载这个程序的就是JVM虚拟机。可以理解为JVM虚拟机就是这个java程序的进程的概念。 Android的进程是什么呢在Zygote孵化器 fork() 进程的时候我们发现它还fork()了虚拟机准备好虚拟机后反射执行了ActivityThread的main()方法换句话说Android中一样也是一个虚拟机对应了一个APP进程。 APP进程中可能有很多线程比如binder线程、用户自己开辟的线程。这些线程的切换都通过TCB来进行现场的保存与恢复。虚拟机栈、本地方法栈、程序计数器在内存中的地址信息都会通过TCB来进行保存和恢复。 我们还要注意到TCB和PCB的保存与恢复类似都是保存数据地址例如栈信息的数据地址、寄存器的数据等。线程的栈中有局部变量表它通过指针JVM中线程共享的部分获取数据它本身并不存储数据。线程私有的是线程各自的局部变量表、程序计数器等信息而并不是数据本身。例如下面这张图所示的虚拟机栈内部结构都是一些指针信息并没有保存数据本体。 到这里我们来梳理一下 App进程的切换本质上是对该进程的PCB程序控制器的信息进行保存与恢复。其信息具体指向的内容仍然还在内存中。线程的切换如果是同一进程下的线程切换本质上是对线程的TCB线程控制器的信息进行保存与恢复。其信息具体指向的内容仍然在对应进程所拥有的内存空间中。 我们来看到下面这张图 为了结构清晰这里没有加入多级Cache缓存主要为了表达如果仅是线程切换只需要切换TCB的信息即可也就是从上图蓝线引用切换为粉线引用而PCB的信息则不需要改动。当然如果要切换其他进程就需要对PCB进行保存和恢复了。 到这我们最初的问题“一个JVM虚拟机的内存区域就是这样的一个线程占有了虚拟机栈、本地方法栈、程序计数器那其他线程到哪去了”。可以回答其他线程的虚拟机栈、本地方法栈、程序计数器还在内存中这三者在内存中的地址信息存在了TCB线程控制块中。当这个线程需要被调度的时候通过TCB的地址信息将这个线程的数据恢复回来。 Java的线程是如何实现的 我们从启动线程的源码中也能看到start()最终调用到start0()这个本地方法这就说明Java线程的启动是由JVM底层来决定的。又提到王道操作系统的内容我们知道线程分为用户线程、核心线程、组合线程。Java线程的实现方式随不同操作系统而异。 在《深入理解JVM虚拟机》中说到例如Windows和Linux中都是使用一对一的线程模型来实现的一条Java线程对应一条轻量级进程。在Unix平台中可以支持一对一以及多对多。但都没有使用用户线程主要原因是如果使用用户线程进程就要自己处理线程的创建 、切换和销毁但最重要的麻烦在于很难处理“阻塞如何处理”、“多处理器时如何将线程映射到其他处理器上并行执行”。 下图给出用户线程与轻量级进程1:1的关系其中UT为用户线程(User Thread)同时也是Java线程LWP为轻量级进程(LowWeight Process)KLT为内核线程Kernel-Level Thread): 也正是有内核线程的参与即由操作系统介入把线程作为最小单位进行调度才使得在多核CPU下一个Java进程的多个线程可以运行在不同的处理机上。

  1. 多核CPU的结构图 铺垫了这么多仍然不能切入主题在讨论线程间可见性问题的时候避不开缓存一致性问题。我们知道ALU计算单元的处理速度远高于内存数据的读写速度所以我们引入了多级缓存来减小这种效率差。为了节省时间我们直接考虑一个JVM虚拟机进程在多级缓存中是如何表现的且我只画了二级缓存 主存的数据很多缓存中只根据局部性原理留下了几个物理块。我们先来梳理几个概念再进入线程间可见性问题。 首先JVM内存区域中的堆和方法区的“共享”概念在上面这张图中就体现了。不同的线程并行跑在不同的CPU中访问的都是JVM内存区域中的“共享”区。但是由于多级缓存Java线程访问到的只是一份数据拷贝并不是真正对主存的数据做处理。我们首先明确一点缓存中的数据是真拷贝缓存是从主存中拷贝了一整个物理块到缓存中来而不是保存了指向主存的指针。 在不同CPU中对共享区域数据的修改在使用“写回法”的情况下只要没出现物理块的调换就不会将更改后的数据更新回主存。其他线程也根本不知道这部分数据被改了 这久发生了线程安全问题到底应该听谁的呢最后写回主存的时候就会无法确认最后的data到底是2还是3。 Java中通过volatile、final、synchronized关键字来实现线程间可见性 为了解决这种缓存一致性问题java引入了volatile、final、synchronized关键字来实现线程间可见性问题。具体如何做到的这里就不做多余分析了。通过线程间可见性关键字让对变量的访问需要强制从内存中重新把数据刷回。
  2. 最后回到正题单核CPU会有线程可见性问题么 答案是没有的。还是刚才的例子现在只有CPU-1首先来到线程A将data从1改为2 接下来进行线程切换线程私有区被切换为了线程B的内容但共有部分并不需要切换这是进程资源只有在进程切换或者缺页等情况发生的时候才会有所变动。 轮到线程B执行线程B也要访问data变量通过局部变量表的指针找到了位于堆中data的位置 由于线程B访问的也是这一块缓存即便是没有缓存一致性协议它仍然能够访问到最新的值因为这里根本就不存在同一级别的缓存出现不一致的情况因为它最终层级只有一个缓存。至此就搞通了为什么单核CPU下的多线程场景不会造成线程安全不会有线程可见性问题。 当然在进行进程切换时或者发生缓存缺页时进程的物理块可能会被逐层写回主存但这并不影响我们之前的分析。 此外本文举的data例子是int变量暂且认为是一个对象中的某个成员变量所以修改的是堆中某个对象的成员变量的值避免掺杂其他问题。 单核CPU还有必要开启多线程吗 还是有必要的举个例子其中一个线程需要进行I/O操作不希望在IO还没完成的时候把CPU时间交给它浪费CPU资源。
  3. 参考文献 《深入理解Java虚拟机》周志明 《王道操作系统考研复习指导》王道论坛