深刻理解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

相關文章
相關標籤/搜索