该线程或进程自上一个步骤以来已更改_多线程与高并发
作者:彭阿三
出自:InfoQ 寫作平臺
原文:xie.infoq.cn/article/fa8bfade7e69b607c4daad8b5
一、概念
1、進程
進程指正在運行的程序,進程擁有一個完整的、私有的基本運行資源集合。通常,每個進程都有自己的內存空間。
進程往往被看作是程序或應用的代名詞,然而,用戶看到的一個單獨的應用程序實際上可能是一組相互 協作的進程集合。
為了便于進程之間的通信,大多數操作系統都支持進程間通信(IPC),如pipes 和sockets。IPC不僅支持同一系統上的通信,也支持不同的系統。IPC通信方式包括管道(包括無名管道和命名管道)、消息隊列、信號量、共享存儲、Socket、Streams等方式,其中 Socket和Streams支持不同主機上的兩個進程IPC。
2、線程
線程有時也被稱為輕量級的進程。進程和線程都提供了一個執行環境,但創建一個新的線程比創建一個 新的進程需要的資源要少。
線程是在進程中存在的 — 每個進程最少有一個線程。線程共享進程的資源,包括內存和打開的文件。這樣提高了效率,但潛在的問題就是線程間的通信。
多線程的執行是Java平臺的一個基本特征。每個應用都至少有一個線程 – 或幾個,如果算上“系統”線程的話,比如內存管理和信號處理等。但是從程序員的角度來看,啟動的只有一個線程,叫主線程。
簡而言之:一個程序運行后至少有一個進程,一個進程中可以包含多個線程。
3、并發和并行
- 并行是指兩個或者多個事件在同一時刻發生;而并發是指兩個或多個事件在同一時間間隔發生。
- 并行是在不同實體上的多個事件,并發是在同一實體上的多個事件。
- 在一臺處理器上“同時”處理多個任務,在多臺處理器上同時處理多個任務。如hadoop分布式集群
二、線程安全
1、基本概念
2、對象的安全
局部基本類型變量:局部變量存儲在線程自己的棧中。也就是說,局部變量永遠也不會被多個線程共享。所以,基礎類型的 局部變量是線程安全的。下面是基礎類型的局部變量的一個例子:
public class ThreadTest { public static voidmain(String[]args){ MyThread share = new MyThread();for (int i=0;i<10;i++){new Thread(share,"線程"+i).start(); }} } class MyThread implementsRunnable{public void run() { int a =0;++a;System.out.println(Thread.currentThread().getName()+":"+a);} } //打印結果 線程0:1 線程1:1 線程2:1 線程3:1 線程4:1 線程5:1 線程6:1 線程7:1 線程8:1 線程9:1無論多少個線程對run()方法中的基本類型a執行++a操作,只是更新當前線程棧的值,不會影響其他線程,也就是不共享數據;
對象的局部引用和基礎類型的局部變量不太一樣,盡管引用本身沒有被共享,但引用所指的對象并沒有存儲在線程的棧內。所有的對象都存在共享堆中。如果在某個方法中創建的對象不會逃逸出(即該對象不會被其它方法獲得,也不會被非局部變量引用 到)該方法,那么它就是線程安全的。實際上,哪怕將這個對象作為參數傳給其它方法,只要別的線程獲取不到這個對象,那它仍是線程安全的。
對象成員存儲在堆上。如果兩個線程同時更新同一個對象的同一個成員,那這個代碼就不是線程安全的。如果兩個線程同時調用同一個實例上的同一個方法并且有更新操作,就會有競態條件問題。
三、JAVA內存模型
1、線程之間的通信
線程的通信是指線程之間以何種機制來交換信息。在命令式編程中,線程之間的通信機制有兩種共享內 存和消息傳遞。Java的并發采用的是共享內存模型。
2、Java 內存模型結構
Java內存模型(簡稱JMM),JMM決定一個線程對共享變量的寫入何時對另一個線程可見。從抽象的角度 來 看,JMM定義了線程和主內存之間的抽象關系:線程之間的共享變量存儲在主內存(main memory) 中, 每個線程都有一個私有的本地內存(local memory),本地內存中存儲了該線程以讀/寫共享變量的副本。 本地內存是JMM的一個抽象概念,并不真實存在。它涵蓋了緩存,寫緩沖區,寄存器以及其他 的硬件和編譯器優化。
從上圖來看,線程A與線程B之間如要通信的話,必須要經歷下面2個步驟:
四、鎖
1、CAS樂觀鎖
樂觀鎖:不加鎖,假設沒有沖突去完成某項操作,如果因為沖突失敗就重試,直到成功為止。其實現方式有一種比較典型的就是Compare and Swap( CAS )。
從思想上來說,Synchronized屬于悲觀鎖,悲觀地認為程序中的并發情況嚴重,所以嚴防死守。CAS屬樂觀鎖,樂觀地認為程序中的并發情況不那么嚴重,所以讓線程不斷去嘗試更新。
CAS的缺點:
1.CPU開銷較大 在并發量比較高的情況下,如果許多線程反復嘗試更新某一個變量,卻又一直更新不成功,循環往復,會給CPU帶來很大的壓力。
2.不能保證代碼塊的原子性 CAS機制所保證的只是一個變量的原子性操作,而不能保證整個代碼塊的原子性。比如需要保證3個變量共同進行原子性的更新,就不得不使用Synchronized了。
五、Synchronized塊
1、概念
Java 中的同步塊用 synchronized 標記。 同步塊在 Java 中是同步在某個對象上。 所有同步在一個對象上 的同步塊在同時只能被一個線程進入并執行操作。所有其他等待進入該同步塊的線程將被阻塞,直到執行該同步塊中的線程退出。
2、Synchronized塊的幾種方式
- 實例方法
- 靜態方法
- 實例方法中的同步塊
- 靜態方法中的同步塊
上述同步塊都同步在不同對象上。實際需要那種同步塊視具體情況而定。
下面是一個同步的實例方法:
//不同實例調用不會阻塞 public class MethodSync {public synchronized void test(){try {System.out.println(Thread.currentThread().getName() + " test 進入了同步方法");Thread.sleep(5000);System.out.println(Thread.currentThread().getName() + " test 休眠結束");} catch (InterruptedException e) {e.printStackTrace();}} } /* * 每個線程都會重新創建一個新的對象所以不會阻塞 */ public class MyThread extends Thread {@Overridepublic void run() {MethodSync sync = new MethodSync();System.out.println(Thread.currentThread().getName() + " test 準備進入");sync.test();} } public class Test {public static void main(String[] args) {new MyThread().start();new MyThread().start();} } //Thread-1 test 準備進入 //Thread-0 test 準備進入 //Thread-1 test 進入了同步方法 //Thread-0 test 進入了同步方法 //Thread-0 test 休眠結束 //Thread-1 test 休眠結束//同一個實例調用會阻塞 public class MethodSync {public synchronized void test1(){try {System.out.println(Thread.currentThread().getName() + " test1 進入了同步方法");Thread.sleep(5000);System.out.println(Thread.currentThread().getName() + " test1 休眠結束");} catch (InterruptedException e) {e.printStackTrace();}} } /** * 每個線程用同一個MethodSync對象調用test1()所以線程阻塞 */ public class MyThread extends Thread {static MethodSync sync = new MethodSync();@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " test 準備進入");sync.test1();} } public class Test {public static void main(String[] args) {new MyThread().start();new MyThread().start();} } //Thread-0 test1 準備進入 //Thread-0 test1 進入了同步方法 //Thread-1 test1 準備進入 //Thread-0 test1 休眠結束 //Thread-1 test1 進入了同步方法 //Thread-1 test1 休眠結束ynchronized關鍵字鎖住了調用當前方法的當前實例,如果不同實例不受同步鎖synchronized關鍵字影響,如果相同實例調用的當前方法則受關鍵字synchronized約束。
同步代碼塊傳參變量對象 (鎖住的是變量對象)
- 同一個屬性對象才會實現同步
一個實例對象的成員屬性肯定是同一個,此處列舉的是不同實例的情況,但是 依舊實現了同步,原因如下:
Integer存在靜態緩存,范圍是-128 ~ 127,當使用Integer A = 127 或者 Integer A = Integer.valueOf(127) 這樣的形式,都是從此緩存拿。如果使用 Integer A = new Integer(127),每次都是一個新的對象。此例中,兩個對象實例的成員變量 lockObject 其實是同一個對象,因此實現了同步。還有字符串常量池也要注意。所以此處關注是,同步代碼塊傳參的對象是否是同一個。這跟第二個方式其實是同一種。
同步代碼塊傳參class對象(全局鎖)
- 所有調用該方法的線程都會實現同步
修飾靜態方法(全局鎖)
JLS規范里面有明確的定義static方法鎖的是 Class objectsynchronized 修飾靜態方法鎖的是類對象,全局鎖。public class MethodSync {//全局鎖,類是全局唯一的public static synchronized void test4() {synchronized (MethodSync.class) {try {System.out.println(Thread.currentThread().getName() + " test4 進入了同步方法");Thread.sleep(5000);System.out.println(Thread.currentThread().getName() + " test4 休眠結束");} catch (InterruptedException e) {e.printStackTrace();}}} } public class MyThread extends Thread {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " test 準備進入");MethodSync.test4();} } public class Test {public static void main(String[] args) {new MyThread().start();new MyThread().start();} } //Thread-0 test4 準備進入 //Thread-1 test4 準備進入 //Thread-0 test4 進入了同步方法 //Thread-0 test4 休眠結束 //Thread-1 test4 進入了同步方法 //Thread-1 test4 休眠結束
靜態方法的synchronized,鎖住了該方法所在的類對象上,因為一個類只能對應一個類對象,所以同時只有一個線程執行類中的靜態同步方法.
總結
以上是生活随笔為你收集整理的该线程或进程自上一个步骤以来已更改_多线程与高并发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c++冒泡排序代码_数据结构和算法必知必
- 下一篇: kubeadm部署k8s_(Ansibl