深刻理解java并发编程基础篇(二)-------线程、进程、Java内存模型

1、前言

  经过前面的学习,咱们了解到一些关于并发编程的一些基本概念,这一篇将继续总结以及复习基础篇的内容。java

2、进程以及线程

2.1 什么是进程?

  进程是程序的一次执行过程,是系统运行程序的基本单位,所以进程是动态的。系统运行一个程序便是一个进程从建立,运行到消亡的过程。编程

  在 Java 中,当咱们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。windows

  以下图所示,在 windows 中经过查看任务管理器的方式,咱们就能够清楚看到 window 当前运行的进程:安全

2.2 什么是线程?

  线程与进程类似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程当中能够产生多个线程。与进程不一样的是同类的多个线程共享进程的堆和方法区资源,但每一个线程有本身的程序计数器、虚拟机栈和本地方法栈,因此系统在产生一个线程,或是在各个线程之间做切换工做时,负担要比进程小得多,也正由于如此,线程也被称为轻量级进程。
  那么下面咱们会从jvm角度来分析线程与进程的关系。性能优化

2.3 图解线程与进程的关系

下面是简略版的图解:多线程


  从上图能够看出:一个进程中能够有多个线程,多个线程共享进程的堆和方法区资源,可是每一个线程有本身的程序计数器、虚拟机栈 和 本地方法栈。

总结: 线程是进程划分红的更小的运行单位。线程和进程最大的不一样在于基本上各进程是独立的,而各线程则不必定,由于同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。并发

3、Java内存模型

  JMM(java内存模型),因为并发程序要比串行程序复杂不少,其中一个重要缘由是并发程序中数据访问一致性和安全性将会受到严重挑战。如何保证一个线程能够看到正确的数据呢?这个问题看起来很白痴。对于串行程序来讲,根本就是小菜一碟,若是你读取一个变量,这个变量的值是1,那么你读取到的必定是1,就是这么简单的问题在并行程序中竟然变得复杂起来。事实上,若是不加控制地任由线程胡乱并行,即便本来是1的数值,你也可能读到2。所以咱们须要在深刻了解并行机制的前提下,再定义一种规则,保证多个线程间能够有小弟,正确地协同工做。而JMM也就是为此而生的。jvm

  JMM关键技术点都是围绕着多线程的原子性、可见性、有序性来创建的。咱们须要先了解这些概念。函数

3.1 原子性

  原子性是指操做是不可分的,要么所有一块儿执行,要么不执行。在java中,其表如今对于共享变量的某些操做,是不可分的,必须连续的完成。好比a++,对于共享变量a的操做,实际上会执行3个步骤:性能

1.读取变量a的值,假如a=1

2.a的值+1,为2

3.将2值赋值给变量a,此时a的值应该为2

这三个操做中任意一个操做,a的值若是被其余线程篡改了,那么都会出现咱们不但愿出现的结果。因此必须保证这3个操做是原子性的,在操做a++的过程当中,其余线程不会改变a的值,若是在上面的过程当中出现其余线程修改了a的值,在知足原子性的原则下,上面的操做应该失败。

java中实现原子操做的方法大体有2种:锁机制、无锁CAS机制,后面的章节中会有介绍。

3.2 可见性

  可见性是值一个线程对共享变量的修改,对于另外一个线程来讲是不是能够看到的。

  首先看一下Java线程内存模型:


  线程须要修改共享资源X,须要先把X从主内存复制一份到线程的工做内存,在本身的工做内存中修改完毕以后,再从工做内存中回写到主内存。若是线程对变量的操做没有刷写回主内存的话,仅仅改变了本身的工做内存的变量的副本,那么对于其余线程来讲是不可见的。而若是另外一个变量没有读取主内存中的新的值,而是使用旧的值的话,一样的也能够列为不可见。

共享变量可见性的实现原理:

1.线程A在本身的工做内存中修改变量以后,须要将变量的值刷新到主内存中
2.线程B要把主内存中变量的值更新到工做内存中

关于线程可见性的控制,可使用volatile、synchronized、锁来实现,后面章节会有详细介绍。

3.3 有序性

  有序性指的是程序按照代码的前后顺序执行。 为了性能优化,编译器和处理器会进行指令冲排序,有时候会改变程序语句的前后顺序。

好比下面的例子:

int a = 1;  //1
	int b = 2;  //2
	int c = a + b;  //3
复制代码

通过编译器以及处理器优化后,有可能会变成下面的顺序:

int b = 2;  //1
	int a = 1;  //2
	int c = a + b;  //3
复制代码

上面这个例子调整了代码执行顺序,可是并不会影响程序执行的最后结果。

那么咱们再来看看一个例子(单例是实现--双重加锁实现方式):

package com.MyMineBug.demoRun.test;

public class Singleton {
	static Singleton instance;

	static Singleton getInstance() {
		if (instance == null) {
			synchronized (Singleton.class) {
				if (instance == null)
					instance = new Singleton();
			}
		}
		return instance;
	}
}
复制代码

未被编译器优化的操做:

指令1:分配一款内存H

指令2:在内存H上初始化Singleton对象

指令3:将H的地址赋值给instance变量

编译器优化后的操做指令:

指令1:分配一块内存W

指令2:将W的地址赋值给instance变量

指令3:在内存W上初始化Singleton对象

若是此刻有多个线程执行这段代码,会出现意想不到的结果。

那么单例模式的建立怎样才是最佳的呢?咱们再后续讨论。

  若是以为还不错,请点个赞!!!

  Share Technology And Love Life

相关文章
相关标签/搜索