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