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