Java并发编程(03):多线程并发访问,同步控制
本文源碼:GitHub·點(diǎn)這里 || GitEE·點(diǎn)這里
一、并發(fā)問(wèn)題
多線程學(xué)習(xí)的時(shí)候,要面對(duì)的第一個(gè)復(fù)雜問(wèn)題就是,并發(fā)模式下變量的訪問(wèn),如果不理清楚內(nèi)在流程和原因,經(jīng)常會(huì)出現(xiàn)這樣一個(gè)問(wèn)題:線程處理后的變量值不是自己想要的,可能還會(huì)一臉懵的說(shuō):這不合邏輯吧?
1、成員變量訪問(wèn)
多個(gè)線程訪問(wèn)類的成員變量,可能會(huì)帶來(lái)各種問(wèn)題。
public class AccessVar01 {public static void main(String[] args) {Var01Test var01Test = new Var01Test() ;VarThread01A varThread01A = new VarThread01A(var01Test) ;varThread01A.start();VarThread01B varThread01B = new VarThread01B(var01Test) ;varThread01B.start();} } class VarThread01A extends Thread {Var01Test var01Test = new Var01Test() ;public VarThread01A (Var01Test var01Test){this.var01Test = var01Test ;}@Overridepublic void run() {var01Test.addNum(50);} } class VarThread01B extends Thread {Var01Test var01Test = new Var01Test() ;public VarThread01B (Var01Test var01Test){this.var01Test = var01Test ;}@Overridepublic void run() {var01Test.addNum(10);} } class Var01Test {private Integer num = 0 ;public void addNum (Integer var){try {if (var == 50){num = num + 50 ;Thread.sleep(3000);} else {num = num + var ;}System.out.println("var="+var+";num="+num);} catch (Exception e){e.printStackTrace();}} }這里案例的流程就是并發(fā)下運(yùn)算一個(gè)成員變量,程序的本意是:var=50,得到num=50,可輸出的實(shí)際結(jié)果是:
var=10;num=60 var=50;num=60VarThread01A線程處理中進(jìn)入休眠,休眠時(shí)num已經(jīng)被線程VarThread01B進(jìn)行一次加10的運(yùn)算,這就是多線程并發(fā)訪問(wèn)導(dǎo)致的結(jié)果。
2、方法私有變量
修改上述的代碼邏輯,把num變量置于方法內(nèi),作為私有的方法變量。
class Var01Test {// private Integer num = 0 ;public void addNum (Integer var){Integer num = 0 ;try {if (var == 50){num = num + 50 ;Thread.sleep(3000);} else {num = num + var ;}System.out.println("var="+var+";num="+num);} catch (Exception e){e.printStackTrace();}} }方法內(nèi)部的變量是私有的,且和當(dāng)前執(zhí)行方法的線程綁定,不會(huì)存在線程間干擾問(wèn)題。
二、同步控制
1、Synchronized關(guān)鍵字
使用方式:修飾方法,或者以控制同步塊的形式,保證多個(gè)線程并發(fā)下,同一時(shí)刻只有一個(gè)線程進(jìn)入方法中,或者同步代碼塊中,從而使線程安全的訪問(wèn)和處理變量。如果修飾的是靜態(tài)方法,作用的是這個(gè)類的所有對(duì)象。
獨(dú)占鎖屬于悲觀鎖一類,synchronized就是一種獨(dú)占鎖,假設(shè)處于最壞的情況,只有一個(gè)線程執(zhí)行,阻塞其他線程,如果并發(fā)高,處理耗時(shí)長(zhǎng),會(huì)導(dǎo)致多個(gè)線程掛起,等待持有鎖的線程釋放鎖。
2、修飾方法
這個(gè)案例和第一個(gè)案例原理上是一樣的,不過(guò)這里雖然在修改值的地方加入的同步控制,但是又挖了一個(gè)坑,在讀取的時(shí)候沒(méi)有限制,這個(gè)現(xiàn)象俗稱臟讀。
public class AccessVar02 {public static void main(String[] args) {Var02Test var02Test = new Var02Test ();VarThread02A varThread02A = new VarThread02A(var02Test) ;varThread02A.start();VarThread02B varThread02B = new VarThread02B(var02Test) ;varThread02B.start();var02Test.readValue();} } class VarThread02A extends Thread {Var02Test var02Test = new Var02Test ();public VarThread02A (Var02Test var02Test){this.var02Test = var02Test ;}@Overridepublic void run() {var02Test.change("my","name");} } class VarThread02B extends Thread {Var02Test var02Test = new Var02Test ();public VarThread02B (Var02Test var02Test){this.var02Test = var02Test ;}@Overridepublic void run() {var02Test.change("you","age");} } class Var02Test {public String key = "cicada" ;public String value = "smile" ;public synchronized void change (String key,String value){try {this.key = key ;Thread.sleep(2000);this.value = value ;System.out.println("key="+key+";value="+value);} catch (InterruptedException e) {e.printStackTrace();}}public void readValue (){System.out.println("讀取:key="+key+";value="+value);} }在線程中,邏輯上已經(jīng)修改了,只是沒(méi)執(zhí)行到,但是在main線程中讀取的value毫無(wú)意義,需要在讀取方法上也加入同步的線程控制。
3、同步控制邏輯
同步控制實(shí)現(xiàn)是基于Object的監(jiān)視器。
- 線程對(duì)Object的訪問(wèn),首先要先獲得Object的監(jiān)視器 ;
- 如果獲取成功,則會(huì)獨(dú)占該對(duì)象 ;
- 其他線程會(huì)掉進(jìn)同步隊(duì)列,線程狀態(tài)變?yōu)樽枞?;
- 等Object的持有線程釋放鎖,會(huì)喚醒隊(duì)列中等待的線程,嘗試重啟獲取對(duì)象監(jiān)視器;
4、修飾代碼塊
說(shuō)明一點(diǎn),代碼塊包含方法中的全部邏輯,鎖定的粒度和修飾方法是一樣的,就寫(xiě)在方法上吧。同步代碼塊一個(gè)很核心的目的,減小鎖定資源的粒度,就如同表鎖和行級(jí)鎖。
public class AccessVar03 {public static void main(String[] args) {Var03Test var03Test1 = new Var03Test() ;Thread thread1 = new Thread(var03Test1) ;thread1.start();Thread thread2 = new Thread(var03Test1) ;thread2.start();Thread thread3 = new Thread(var03Test1) ;thread3.start();} } class Var03Test implements Runnable {private Integer count = 0 ;public void countAdd() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized(this) {count++ ;System.out.println("count="+count);}}@Overridepublic void run() {countAdd() ;} }這里就是鎖定count處理這個(gè)動(dòng)作的核心代碼邏輯,不允許并發(fā)處理。
5、修飾靜態(tài)方法
靜態(tài)方法屬于類層級(jí)的方法,對(duì)象是不可以直接調(diào)用的。但是synchronized修飾的靜態(tài)方法鎖定的是這個(gè)類的所有對(duì)象。
public class AccessVar04 {public static void main(String[] args) {Var04Test var04Test1 = new Var04Test() ;Thread thread1 = new Thread(var04Test1) ;thread1.start();Var04Test var04Test2 = new Var04Test() ;Thread thread2 = new Thread(var04Test2) ;thread2.start();} } class Var04Test implements Runnable {private static Integer count ;public Var04Test (){count = 0 ;}public synchronized static void countAdd() {System.out.println(Thread.currentThread().getName()+";count="+(count++));}@Overridepublic void run() {countAdd() ;} }如果不是使用同步控制,從邏輯和感覺(jué)上,輸出的結(jié)果應(yīng)該如下:
Thread-0;count=0 Thread-1;count=0加入同步控制之后,實(shí)際測(cè)試輸出結(jié)果:
Thread-0;count=0 Thread-1;count=16、注意事項(xiàng)
- 繼承中子類覆蓋父類方法,synchronized關(guān)鍵字特性不能繼承傳遞,必須顯式聲明;
- 構(gòu)造方法上不能使用synchronized關(guān)鍵字,構(gòu)造方法中支持同步代碼塊;
- 接口中方法,抽象方法也不支持synchronized關(guān)鍵字 ;
三、Volatile關(guān)鍵字
1、基本描述
Java內(nèi)存模型中,為了提升性能,線程會(huì)在自己的工作內(nèi)存中拷貝要訪問(wèn)的變量的副本。這樣就會(huì)出現(xiàn)同一個(gè)變量在某個(gè)時(shí)刻,在一個(gè)線程的環(huán)境中的值可能與另外一個(gè)線程環(huán)境中的值,出現(xiàn)不一致的情況。
使用volatile修飾成員變量,不能修飾方法,即標(biāo)識(shí)該線程在訪問(wèn)這個(gè)變量時(shí)需要從共享內(nèi)存中獲取,對(duì)該變量的修改,也需要同步刷新到共享內(nèi)存中,保證了變量對(duì)所有線程的可見(jiàn)性。
2、使用案例
class Var05Test {private volatile boolean myFlag = true ;public void setFlag (boolean myFlag){this.myFlag = myFlag ;}public void method() {while (myFlag){try {System.out.println(Thread.currentThread().getName()+myFlag);Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}} }3、注意事項(xiàng)
- 可見(jiàn)性只能確保每次讀取的是最新的值,但不支持變量操作的原子性;
- volatile并不會(huì)阻塞線程方法,但是同步控制會(huì)阻塞;
- Java同步控制的根本:保證并發(fā)下資源的原子性和可見(jiàn)性;
四、源代碼地址
GitHub·地址 https://github.com/cicadasmile/java-base-parent GitEE·地址 https://gitee.com/cicadasmile/java-base-parent總結(jié)
以上是生活随笔為你收集整理的Java并发编程(03):多线程并发访问,同步控制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 文件系统(01):基于SpringBoo
- 下一篇: JavaEE基础(06):Servlet