“非線程安全”有可能導(dǎo)致“臟讀”,即取到的數(shù)據(jù)被更改過的。
當(dāng)多個線程共同訪問1個對象的實例變量,可能會不安全。“線程安全”——獲得的實例變量的值經(jīng)過同步處理
把變量放在方法里面,這時變量是私有的,就是安全的。synchronized()生成的是對象鎖因為當(dāng)多個線程訪問多個對象時,JVM會創(chuàng)建多個鎖,比如,A線程要調(diào)用對象X的鎖的方法,B線程要調(diào)用對象M的鎖的方法,B不用等A執(zhí)行完,就可以調(diào)用M的鎖。
如果沒有多個線程對同一個對象的實例變量操作,就不會出現(xiàn)線程不安全問題,不需要用同步。
讀取實例變量時,此值已經(jīng)被其他線程更改過了。
用synchronized解決在鎖內(nèi)部調(diào)用本類的其他synchronized方法,永遠可以得到鎖。
例如:
public class Service { PRivate String usernameParam; private String passWordParam; public void setUsernamePassword (String username,String password) { try { String anyString = new String(); synchronized (anyString) { System.out.println ("線程名稱為:"+Thread.currentThread().getName()+"在"+System.currentTimeMillis()+"進入同步塊"); usernameParam = username; Thread.sleep(3000); passwordParam = password; System.out.println("線程名稱為:"+Thread.currentThread().getName()+"在"+System.currentTimeMillis()+"離開同步塊"); } } catch (InterruptedException e) { e.printStackTrace(); } }}運行結(jié)果
線程名稱為:A在1403594007194進入同步塊 線程名稱為:B在1403594007194進入同步塊 線程名稱為:B在1403594010194離開同步塊 線程名稱為:A在1403594010194離開同步塊
從結(jié)果可看出是異步的,因為不是同一個鎖,這時候很容易出現(xiàn)“臟讀”
synchronized用在static靜態(tài)方法上就是對當(dāng)前的*.java文件對應(yīng)的Class類進行持鎖(整個類鎖上了)。
與加到非static方法的使用效果是一樣的 本質(zhì)上的不同是synchronized加到static方法是給Class類上鎖,synchronized加到非靜態(tài)方法上是給對象上鎖
例如:
public class Service { synchronized public static void printA(){ try{ System.out.println("進入printA"); Thread.sleep(3000); System.out.println("離開printA"); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public static void printB(){ System.out.println("進入printB"); System.out.println("離開printB"); } synchronized public void printC() { System.out.println("進入printC"); System.out.println("離開printC"); }}上面的printA方法和printB方法都是對Service類上鎖的,而printC方法是對對象上鎖的,所以同樣運行時,printC是異步的,printA和printB是同步的。 Class鎖對類的所有對象實例起作用,即不同對象但是靜態(tài)的同步方法仍是同步運行 同步synchronized(class)代碼塊的作用和synchronized static 方法的作用一樣,看下面例子如何上鎖——
public class Service { public static void printA(){ synchronized (Service.class) { try{ System.out.println("進入printA"); Thread.sleep(3000); System.out.println("離開printA"); } catch (InterruptedException e) { e.printStackTrace(); } } }}運行結(jié)果:
A B B A A B B A A B B A 交替打印,持有的鎖不是一個。
上面代碼會造成死循環(huán)使其他同步方法無法運行,修改成用同步塊
Object object1 = new Object();public void methodA() { synchronized (object1) { System.out.println("methodA begin"); boolean isContinueRun = true; while (isContinueRun) { } System.out.println("methodA end"); }}Object object2 = new Object();public void methodB() { synchronized (object2) { System.out.println("methodB begin"); System.out.println("methodB end"); }}“死鎖”必須避免,不同線程在等待不可能被釋放的鎖會導(dǎo)致所有任務(wù)無法完成,造成線程的“假死”。
用JDK的工具監(jiān)測是否有死鎖現(xiàn)象 1. 進入CMD工具 2. 進入JDK安裝文件夾的bin目錄 3. 執(zhí)行jps命令 4. 執(zhí)行jstack命令

什么是內(nèi)置類 看例子:
public class PublicClass { private String username; class PrivateClass { private String age; public String getAge(){ return age; } public void setAge(String age){ this.age = age; } } public String getUsername(){ return username; } public void setUsername(String username){ this.username = username; }}public class Run { public static void main(String[] args) { PublicClass publicClass = new PublicClass(); publicClass.setUsername("a"); System.out.println(publicClass.getUsername()); PrivateClass privateClass = publicClass.new PrivateClass(); //是這樣實例化的,如果PublicClass.java和Run.java不在同一個包中,則需要將PrivateClass內(nèi)置聲明成public privateClass.setAge("18"); System.out.println(privateClass.getAge()); }}內(nèi)置類中還有一種叫靜態(tài)內(nèi)置類,看以下例子:
public class PublicClass { static private String username; static class PrivateClass { static private String age; public String getAge(){ return age; } public void setAge(String age){ this.age = age; } } public String getUsername(){ return username; } public void setUsername(String username){ this.username = username; }}public class Run { public static void main(String[] args) { PublicClass publicClass = new PublicClass(); publicClass.setUsername("a"); System.out.println(publicClass.getUsername()); PrivateClass privateClass = new PrivateClass(); //實例化 privateClass.setAge("18"); System.out.println(privateClass.getAge()); }}結(jié)果,要在method1()運行完之后method2()才能運行。
作用:(使變量在多個線程間可見)強制從公共堆棧中取得變量的值,而不是從線程私有數(shù)據(jù)棧中取得變量的值。
如果有“多繼承”的情況,則也要用實現(xiàn)Runnable接口的方式來處理多線程的問題。
P121 在JVM設(shè)置為Server服務(wù)器的環(huán)境中,線程會一直在私有堆棧中取得值為true,而thread.setRunning(false)更新的是公共堆棧的變量值false,所以會出現(xiàn)死循環(huán)。 問題是–私有堆棧中的值和公共堆棧中的值不同步造成的。 解決問題–要用volatile關(guān)鍵字 當(dāng)線程訪問isRunning時,強制性從公共堆棧中取值

volatile最致命的缺點是不支持原子性。
線程安全包含原子性和可見性兩個方面。
volatile不具備同步性,也就不具備原子性。 主要用于多線程讀取共享變量時,獲取最新值使用。
注意:如果改變實例變量中的數(shù)據(jù),比如i++,也就是i=i+1 這樣的操作不是一個原子操作,也就是非線程安全。 步驟分解如下: 1. 從內(nèi)存中取出i的值 2. 計算i的值 3. 將i的值寫到內(nèi)存
假如在第二步計算值時,另一個線程也修改i的值,這時會出現(xiàn)“臟讀”,這是需要用synchronized來解決

多線程環(huán)境中,use和assign是多次出現(xiàn),這一操作不是原子性,所以在read和load之后,如果主內(nèi)存count變量發(fā)生修改之后,線程工作內(nèi)存中的值由于已經(jīng)加載,不會發(fā)生對應(yīng)的變化,也就是私有內(nèi)存和公共內(nèi)存的變量不同步,所以計算出來的和預(yù)期的不一樣,就出現(xiàn)非線程安全問題。
一個原子類型就是一個原子操作可用的類型,它可以在沒有鎖的情況下做到線程安全。
public class AddCountThread extends Thread { private AtomicInteger count = new AtomicInteger(0); @Override public void run(){ for(int i = 0;i < 10000; i++) { System.out.println(count.incrementAndGet()); } }}public class Run { public static void main (String[] args) { AddCountThread countService = new AddCountThread(); Thread t1 = new Thread(countService); t1.start(); Thread t2 = new Thread(countService); t2.start(); Thread t3 = new Thread(countService); t3.start(); Thread t4 = new Thread(countService); t4.start(); Thread t5 = new Thread(countService); t5.start(); }}運行結(jié)果成功累加到50000
原子類在具有邏輯性的情況下輸出結(jié)果也具有隨機性。 - addAndGet()這些原子類的方法是原子的,但是方法與方法之間的調(diào)用不是原子的。
新聞熱點
疑難解答
圖片精選