本文源碼: GitHub·點(diǎn)這里 || GitEE·點(diǎn)這里
創(chuàng)新互聯(lián)公司是一家專注于做網(wǎng)站、成都網(wǎng)站設(shè)計與策劃設(shè)計,齊河網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)公司做網(wǎng)站,專注于網(wǎng)站建設(shè)10多年,網(wǎng)設(shè)計領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:齊河等地區(qū)。齊河做網(wǎng)站價格咨詢:18982081108
多線程學(xué)習(xí)的時候,要面對的第一個復(fù)雜問題就是,并發(fā)模式下變量的訪問,如果不理清楚內(nèi)在流程和原因,經(jīng)常會出現(xià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 ;
}
@Override
public void run() {
var01Test.addNum(50);
}
}
class VarThread01B extends Thread {
Var01Test var01Test = new Var01Test() ;
public VarThread01B (Var01Test var01Test){
this.var01Test = var01Test ;
}
@Override
public 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)算一個成員變量,程序的本意是:var=50,得到num=50,可輸出的實(shí)際結(jié)果是:
var=10;num=60
var=50;num=60
VarThread01A線程處理中進(jìn)入休眠,休眠時num已經(jīng)被線程VarThread01B進(jìn)行一次加10的運(yùn)算,這就是多線程并發(fā)訪問導(dǎo)致的結(jié)果。
修改上述的代碼邏輯,把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í)行方法的線程綁定,不會存在線程間干擾問題。
使用方式:修飾方法,或者以控制同步塊的形式,保證多個線程并發(fā)下,同一時刻只有一個線程進(jìn)入方法中,或者同步代碼塊中,從而使線程安全的訪問和處理變量。如果修飾的是靜態(tài)方法,作用的是這個類的所有對象。
獨(dú)占鎖屬于悲觀鎖一類,synchronized就是一種獨(dú)占鎖,假設(shè)處于最壞的情況,只有一個線程執(zhí)行,阻塞其他線程,如果并發(fā)高,處理耗時長,會導(dǎo)致多個線程掛起,等待持有鎖的線程釋放鎖。
這個案例和第一個案例原理上是一樣的,不過這里雖然在修改值的地方加入的同步控制,但是又挖了一個坑,在讀取的時候沒有限制,這個現(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 ;
}
@Override
public void run() {
var02Test.change("my","name");
}
}
class VarThread02B extends Thread {
Var02Test var02Test = new Var02Test ();
public VarThread02B (Var02Test var02Test){
this.var02Test = var02Test ;
}
@Override
public 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("讀?。簁ey="+key+";value="+value);
}
}
在線程中,邏輯上已經(jīng)修改了,只是沒執(zhí)行到,但是在main線程中讀取的value毫無意義,需要在讀取方法上也加入同步的線程控制。
同步控制實(shí)現(xiàn)是基于Object的監(jiān)視器。
說明一點(diǎn),代碼塊包含方法中的全部邏輯,鎖定的粒度和修飾方法是一樣的,就寫在方法上吧。同步代碼塊一個很核心的目的,減小鎖定資源的粒度,就如同表鎖和行級鎖。
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);
}
}
@Override
public void run() {
countAdd() ;
}
}
這里就是鎖定count處理這個動作的核心代碼邏輯,不允許并發(fā)處理。
靜態(tài)方法屬于類層級的方法,對象是不可以直接調(diào)用的。但是synchronized修飾的靜態(tài)方法鎖定的是這個類的所有對象。
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++));
}
@Override
public void run() {
countAdd() ;
}
}
如果不是使用同步控制,從邏輯和感覺上,輸出的結(jié)果應(yīng)該如下:
Thread-0;count=0
Thread-1;count=0
加入同步控制之后,實(shí)際測試輸出結(jié)果:
Thread-0;count=0
Thread-1;count=1
Java內(nèi)存模型中,為了提升性能,線程會在自己的工作內(nèi)存中拷貝要訪問的變量的副本。這樣就會出現(xiàn)同一個變量在某個時刻,在一個線程的環(huán)境中的值可能與另外一個線程環(huán)境中的值,出現(xiàn)不一致的情況。
使用volatile修飾成員變量,不能修飾方法,即標(biāo)識該線程在訪問這個變量時需要從共享內(nèi)存中獲取,對該變量的修改,也需要同步刷新到共享內(nèi)存中,保證了變量對所有線程的可見性。
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();
}
}
}
}
GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent
新聞標(biāo)題:Java并發(fā)編程(03):多線程并發(fā)訪問,同步控制
當(dāng)前URL:http://bm7419.com/article36/gejgpg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、品牌網(wǎng)站建設(shè)、網(wǎng)站營銷、做網(wǎng)站、軟件開發(fā)、外貿(mào)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)