摘要:架構(gòu)師入門(mén)筆記一初識(shí)線程關(guān)鍵字本章節(jié)主要介紹線程的關(guān)鍵字,的含義,使用方法,使用場(chǎng)景,以及注意事項(xiàng)。若次方法也加上了,就必須等待線程執(zhí)行完后,才能調(diào)用關(guān)鍵字不具備關(guān)鍵字的原子性同步其主要作用就是使變量在多個(gè)線程中可見(jiàn)。
架構(gòu)師入門(mén)筆記一 初識(shí)線程關(guān)鍵字
本章節(jié)主要介紹線程的關(guān)鍵字 synchronized,volatile 的含義,使用方法,使用場(chǎng)景,以及注意事項(xiàng)。
線程安全首先我們要了解線程安全的概念:當(dāng)多個(gè)線程訪問(wèn)某一個(gè)類(對(duì)象或方法)時(shí),這個(gè)類始終都能表現(xiàn)出正確的行為,那么這個(gè)類(對(duì)象或方法)就是線程安全的。
要如何確保類能表現(xiàn)出正確的行為,這就需要關(guān)鍵字出馬!
synchronized 可以在任意對(duì)象及方法上加鎖,而加鎖的這段代碼稱為"互斥區(qū)"或"臨界區(qū)"
其工作原理:當(dāng)一個(gè)線程想要執(zhí)行synchronized修飾的方法,必須經(jīng)過(guò)以下三個(gè)步驟
step1 嘗試獲得鎖
step2 如果拿到鎖,執(zhí)行synchronized代碼體內(nèi)容
step3 如果拿不到鎖,這個(gè)線程就會(huì)不斷地嘗試獲得這把鎖,直到拿到為止。這個(gè)過(guò)程可能是多個(gè)線程同時(shí)去競(jìng)爭(zhēng)這把鎖(鎖競(jìng)爭(zhēng)的問(wèn)題)。
注*(多個(gè)線程執(zhí)行的順序是按照CPU分配的先后順序而定的,而并非代碼執(zhí)行的先后順序)
使用方法介紹:
synchronized 重入
在使用synchronized時(shí),當(dāng)一個(gè)線程得到了一個(gè)對(duì)象的鎖后,再次請(qǐng)求此對(duì)象時(shí)是可以再次得到該對(duì)象的鎖。
public class MySyncReentrant { /** * 重入調(diào)用 */ private synchronized void method1() { System.out.println("^^^^^^^^^^^^^method1"); method2(); } private synchronized void method2() { System.out.println("-----------------------method2"); method3(); } private synchronized void method3() { System.out.println("********************method3"); } public static void main(String[] args) { final MySyncReentrant mySyncReentrant = new MySyncReentrant(); Thread thread = new Thread(new Runnable() { @Override public void run() { mySyncReentrant.method1(); } }, "reentrant"); thread.start(); Thread thread2 = new Thread(new Runnable() { @Override public void run() { SunClass sunClass = new SunClass(); sunClass.sunMethod(); } }); thread2.start(); } /** * 有父子繼承關(guān)系的類,如果都使用了synchronized關(guān)鍵字,也是線程安全的。 */ static class FatherClass { public synchronized void fatherMethod(){ System.out.println("fatherMethod...."); } } static class SunClass extends FatherClass{ public synchronized void sunMethod() { System.out.println("sunMethod...."); this.fatherMethod(); } } }
synchronized 代碼塊:
如果被修飾的方法執(zhí)行需要很長(zhǎng)時(shí)間,線程之間等待的時(shí)間就會(huì)很長(zhǎng),所以將synchronized 修飾在代碼塊上是可以優(yōu)化執(zhí)行時(shí)間。(這也叫減少鎖的粒度)
synchronized (this) {} , 可以是 this(對(duì)象鎖) class(類鎖) Object lock = new Object(); 任何對(duì)象鎖。
import java.util.concurrent.atomic.AtomicInteger; public class CodeBlockLock { // 對(duì)象鎖 private void thisLock () { synchronized (this) { System.out.println("this 對(duì)象鎖!"); } } // 類鎖 private void classLock () { synchronized (CodeBlockLock.class) { System.out.println("class 類鎖!"); } } // 任何對(duì)象鎖 private Object lock = new Object(); private void objectLock () { synchronized (lock) { System.out.println("object 任何對(duì)象鎖!"); } } // 字符串鎖,注意String常量池的緩存功能 private void stringLock () { synchronized ("string") { // new String("string") try { for(int i = 0; i < 3; i++) { System.out.println("thread : " + Thread.currentThread().getName() + " stringLock !"); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } } // 字符串鎖改變 private String strLock = "lock"; private void changeStrLock () { synchronized (strLock) { try { System.out.println("thread : " + Thread.currentThread().getName() + " changeLock start !"); strLock = "changeLock"; Thread.sleep(5000); System.out.println("thread : " + Thread.currentThread().getName() + " changeLock end !"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { final CodeBlockLock codeBlockLock = new CodeBlockLock(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { codeBlockLock.thisLock(); } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { codeBlockLock.classLock(); } }); Thread thread3 = new Thread(new Runnable() { @Override public void run() { codeBlockLock.objectLock(); } }); thread1.start(); thread2.start(); thread3.start(); // 如果字符串鎖,用new String("string") t4,t5線程是可以獲取鎖的,如果直接使用"string" ,若鎖不釋放,t5線程一直處理等待中 Thread thread4 = new Thread(new Runnable() { @Override public void run() { codeBlockLock.stringLock(); } }, "t4"); Thread thread5 = new Thread(new Runnable() { @Override public void run() { codeBlockLock.stringLock(); } }, "t5"); thread4.start(); thread5.start(); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } // 字符串變了,鎖也會(huì)改變,導(dǎo)致t7線程在t6線程未結(jié)束后變開(kāi)始執(zhí)行,但一個(gè)對(duì)象的屬性變了,不影響這個(gè)對(duì)象的鎖。 Thread thread6 = new Thread(new Runnable() { @Override public void run() { codeBlockLock.changeStrLock(); } }, "t6"); Thread thread7 = new Thread(new Runnable() { @Override public void run() { codeBlockLock.changeStrLock(); } }, "t7"); thread6.start(); thread7.start(); } }
運(yùn)行結(jié)果:
this 對(duì)象鎖! class 類鎖! object 任何對(duì)象鎖! thread : t4 stringLock ! thread : t4 stringLock ! thread : t4 stringLock ! thread : t5 stringLock ! thread : t5 stringLock ! thread : t5 stringLock ! thread : t6 changeLock start ! thread : t7 changeLock start ! thread : t6 changeLock end ! thread : t7 changeLock end !
注* 給String的常量加鎖,容易會(huì)出現(xiàn)死循環(huán)的情況。 如果加鎖的字符串變了,鎖也會(huì)變。若一個(gè)對(duì)象的屬性變了,是不影響這個(gè)對(duì)象的鎖。static + synchronized 一起使用 是類級(jí)別的鎖
synchronized 異常:
synchronized 遇到異常后,自動(dòng)釋放鎖,讓其他線程調(diào)用。如果第一個(gè)線程在執(zhí)行任務(wù)時(shí),因?yàn)楫惓?dǎo)致業(yè)務(wù)邏輯未能正常執(zhí)行。后續(xù)的線程執(zhí)行的任務(wù)也都是異常的。所以在編寫(xiě)代碼時(shí)一定要考慮周全
同步的概念就是共享,其目標(biāo)就是為了線程安全(線程安全的兩個(gè)特性:原子性和可見(jiàn)性),A線程獲取對(duì)象的鎖,若B線程想要執(zhí)行synchronized方法,就需要等待,這就是同步。
異步的概念就是獨(dú)立,A線程獲取對(duì)象的鎖,若B線程想要執(zhí)行非synchronized方法,是無(wú)需等待的,這就是異步??梢詤⒖糰jax請(qǐng)求。
public class SyncAndAsyn { private synchronized void syncMethod() { try { System.out.println(Thread.currentThread().getName() + " synchronized method!"); Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } } // 若次方法也加上了 synchronized,就必須等待t1線程執(zhí)行完后,t2才能調(diào)用 private void asynMethod() { System.out.println(Thread.currentThread().getName() + " asynchronized method!"); } public static void main(String[] args) { final SyncAndAsyn syncAndAsyn = new SyncAndAsyn(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { syncAndAsyn.syncMethod(); } }, "t1"); Thread thread2 = new Thread(new Runnable() { @Override public void run() { syncAndAsyn.asynMethod(); } }, "t2"); thread1.start(); thread2.start(); } }volatile
volatile關(guān)鍵字不具備synchronized關(guān)鍵字的原子性(同步)其主要作用就是使變量在多個(gè)線程中可見(jiàn)。
原理圖:
在java中,每一個(gè)線程都會(huì)有一塊工作內(nèi)存區(qū),其中存放著所有線程共享的主內(nèi)存中的變量值的拷貝。當(dāng)線程執(zhí)行時(shí),他在自己的工作內(nèi)存區(qū)中操作這些變量。
為了存取一個(gè)共享的變量,一個(gè)線程通常先獲取鎖定并去清除它的內(nèi)存工作區(qū),把這些共享變量從所有線程的共享內(nèi)存區(qū)中正確的裝入到他自己的所在的工作內(nèi)存區(qū)中。當(dāng)線程解鎖時(shí)保證該工作內(nèi)存區(qū)中變量的值寫(xiě)回到共享內(nèi)存中。
而volatile的作用就是強(qiáng)制線程到主內(nèi)存里面去讀取變量,而不去線程工作內(nèi)存區(qū)里面讀,從而實(shí)現(xiàn)了多個(gè)線程間的變量可見(jiàn)。也就是滿足線程安全的可見(jiàn)性。
可見(jiàn)性:(被volatile修飾的變量,線程執(zhí)行引擎是直接從主內(nèi)存中讀取變量的值)
public class VolatileThread extends Thread{ // 如果不加 volatile,會(huì)導(dǎo)致 "thread end !" 一直沒(méi)有打印, private volatile boolean flag = true; @Override public void run() { System.out.println("thread start !"); while (flag) { } System.out.println("thread end !"); } public static void main(String[] args) { VolatileThread thread = new VolatileThread(); thread.start(); try { // 等線程啟動(dòng)了,再設(shè)置值 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } thread.setFlag(false); System.out.println("flag : " + thread.isFlag()); } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } }
volatile 不具備原子性:(多線程之間不是同步的,存在線程安全,從下面的例子中可以得知:如果是同步的,最后一次打印絕對(duì)是1000*10 。為了彌補(bǔ)這個(gè)問(wèn)題,可以考慮使用atomic類的系類對(duì)象)
import java.util.concurrent.atomic.AtomicInteger; /** * volatile關(guān)鍵字不具備synchronized關(guān)鍵字的原子性(同步) */ public class VolatileNoAtomic extends Thread{ // 多次執(zhí)行程序,會(huì)發(fā)現(xiàn)最后打印的結(jié)果不是1000的整數(shù)倍.中途打印不是1000的整數(shù)倍,可能是因?yàn)镾ystem.out打印的延遲造成的 // private static volatile int count; private static AtomicInteger count = new AtomicInteger(0); // 不會(huì)出現(xiàn)以上的情況 private static void addCount(){ for (int i = 0; i < 1000; i++) { // count++ ; count.incrementAndGet(); } System.out.println(count); } public void run(){ addCount(); } public static void main(String[] args) { VolatileNoAtomic[] arr = new VolatileNoAtomic[10]; for (int i = 0; i < 10; i++) { arr[i] = new VolatileNoAtomic(); } // 執(zhí)行10個(gè)線程 for (int i = 0; i < 10; i++) { arr[i].start(); } } }
其實(shí)atomic類 并非完美,它也只能保證自己方法是原子性,若要保證多次操作也是原子性,就需要synchronized的幫忙(若不用synchronized修飾,打印的結(jié)果中會(huì)出現(xiàn)非10倍數(shù)的信息,需多次執(zhí)行才能模擬出來(lái))
import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class AtomicUse { private static AtomicInteger count = new AtomicInteger(0); //多個(gè)addAndGet在一個(gè)方法內(nèi)是非原子性的,需要加synchronized進(jìn)行修飾,保證4個(gè)addAndGet整體原子性 . /**synchronized*/ public int multiAdd(){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } count.addAndGet(1); count.addAndGet(2); count.addAndGet(3); count.addAndGet(4); //+10 return count.get(); } public static void main(String[] args) { final AtomicUse au = new AtomicUse(); Listts = new ArrayList (); for (int i = 0; i < 100; i++) { ts.add(new Thread(new Runnable() { @Override public void run() { System.out.println(au.multiAdd()); } })); // 添加100個(gè)線程 } for(Thread t : ts){ t.start(); } } }
以上便是初識(shí)線程關(guān)鍵字的內(nèi)容,方便自己以后查閱,也希望對(duì)讀者有些幫助。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/70526.html
摘要:架構(gòu)師入門(mén)筆記三初識(shí)隊(duì)列和模擬基礎(chǔ)知識(shí)線程通信概念線程是操作系統(tǒng)中獨(dú)立的個(gè)體,但這些個(gè)體如果不經(jīng)過(guò)特殊的處理,就不能成為一個(gè)整體,線程之間的通信就成為整體的必用方法之一。它是一個(gè)基于鏈接節(jié)點(diǎn)的無(wú)界限線程安全隊(duì)列。 架構(gòu)師入門(mén)筆記三 初識(shí)Queue隊(duì)列 wait和notify模擬Queue wait/notify 基礎(chǔ)知識(shí) 線程通信概念:線程是操作系統(tǒng)中獨(dú)立的個(gè)體,但這些個(gè)體如果不經(jīng)過(guò)特...
摘要:多線程術(shù)語(yǔ)辨析任務(wù)和線程是不同的中類本身不執(zhí)行任何操作它只驅(qū)動(dòng)賦予它的任務(wù)而才是定義任務(wù)的地方創(chuàng)建任務(wù)的方式有兩種實(shí)現(xiàn)接口中的方法查看源碼可以看到只有一個(gè)方法使用直接繼承即可這樣就創(chuàng)建了一個(gè)任務(wù)暗示調(diào)度器該線程可以讓出資源了中實(shí)現(xiàn)方法部分源 java多線程 1. 術(shù)語(yǔ)辨析 任務(wù)和線程是不同的,Java中Thread類本身不執(zhí)行任何操作,它只驅(qū)動(dòng)賦予它的任務(wù),而Runnable才是定義任...
摘要:如果僅依靠程序自動(dòng)交出控制的話,那么一些惡意程序?qū)?huì)很容易占用全部時(shí)間而不與其他任務(wù)共享。多個(gè)操作可以在重疊的時(shí)間段內(nèi)進(jìn)行。 PHP下的異步嘗試系列 如果你還不太了解PHP下的生成器,你可以根據(jù)下面目錄翻閱 PHP下的異步嘗試一:初識(shí)生成器 PHP下的異步嘗試二:初識(shí)協(xié)程 PHP下的異步嘗試三:協(xié)程的PHP版thunkify自動(dòng)執(zhí)行器 PHP下的異步嘗試四:PHP版的Promise ...
摘要:上面需要了解的是這倆個(gè)版本都是破蛹成蝶的版本世界挑戰(zhàn)榜咋才前三還沒(méi)擠進(jìn)去呀,你想想世界上有幾千中編程語(yǔ)言,在其中脫穎出來(lái),可以說(shuō)是天之?huà)勺?,鳳毛麟角了。支持正版圖靈上面買(mǎi)吧,如果沒(méi)錢(qián)買(mǎi)盜版吧學(xué)完以后買(mǎi)本正版支持一下,創(chuàng)作不易是吧 ...
閱讀 3457·2021-11-24 09:38
閱讀 1443·2021-11-22 15:08
閱讀 1542·2021-09-29 09:35
閱讀 551·2021-09-02 15:11
閱讀 1359·2019-08-30 12:55
閱讀 439·2019-08-29 17:16
閱讀 555·2019-08-29 11:30
閱讀 474·2019-08-26 13:23