synchronized同步方法
一、方法內的變量是線程安全的
“非線程安全”問題存在於“實例變量”中,如果是方法內部的私有變量,則不存在“非線程安全”問題,所得結果也就是“線程安全”的了
二、實例變量非線程安全
如果多個線程共同訪問1個對象中的實例變量,則有可能出現“非線程安全”問題
用線程訪問的對象中如果有多個實例變量,則運行的結果有可能出現交叉的情況。
如果對象僅有一個實例變量,則有可能出現覆蓋的情況
public class HasSelfPrivateNum { private int num = 0; public void addI(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over!"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over!"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { HasSelfPrivateNum numRef = new HasSelfPrivateNum(); ThreadA a = new ThreadA(numRef); a.start(); ThreadB b = new ThreadB(numRef); b.start(); } } class ThreadA extends Thread{ private HasSelfPrivateNum numRef; public ThreadA(HasSelfPrivateNum numRef){ super(); this.numRef = numRef; } @Override public void run(){ super.run(); numRef.addI("a"); } } class ThreadB extends Thread{ private HasSelfPrivateNum numRef; public ThreadB(HasSelfPrivateNum numRef){ super(); this.numRef = numRef; } @Override public void run(){ super.run(); numRef.addI("b"); } }
運行結果:
a set over! b set over! b num=200 a num=200
兩個線程同時訪問num,出現了值覆蓋
解決辦法是方法前面加上synchronized
//在方法前面加上了 synchronized
synchronized public void addI(String username) {
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
運行結果
a set over! a num=100 b set over! b num=200
- 運行結果是期望結果。
結論:在兩個線程訪問同一個對象中的同步方法時一定是線程安全的
三、多個對象多個鎖
public class HasSelfPrivateNum { private int num = 0; synchronized public void addI(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over!"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over!"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { HasSelfPrivateNum numRef1 = new HasSelfPrivateNum(); HasSelfPrivateNum numRef2 = new HasSelfPrivateNum(); ThreadA a = new ThreadA(numRef1); a.start(); ThreadB b = new ThreadB(numRef2); b.start(); } } class ThreadA extends Thread{ private HasSelfPrivateNum numRef; public ThreadA(HasSelfPrivateNum numRef){ super(); this.numRef = numRef; } @Override public void run(){ super.run(); numRef.addI("a"); } } class ThreadB extends Thread{ private HasSelfPrivateNum numRef; public ThreadB(HasSelfPrivateNum numRef){ super(); this.numRef = numRef; } @Override public void run(){ super.run(); numRef.addI("b"); } }
運行結果:
a set over! b set over! b num=200 a num=100
上面是兩個線程分別訪問同一個類的兩個不同實例的相同名稱的同步方法,效果確是以異步運行的
解釋:這個示例由於創建了2個業務對象,在系統中產生了2個鎖,所以運行結果是異步的,打印效果是先打印b後打印a
為什麽使用了synchronized打印順序卻不是同步的,是交叉的?
關鍵字synchronized取得的鎖是對象鎖,而不是把一段代碼或方法(函數)當做鎖,所以在上面的示例中,哪個線程先執行帶synchronized關鍵字的方法,哪個線程就持有該方法所有屬性的鎖Lock,那麽其他線程只能呈等待狀態,前提是多個線程訪問的是同一個對象。但是如果是多個線程訪問多個對象,則JVM會創建多個鎖。上面的示例就是創建了2個HasSelfPrivateNum.java類的對象,所以就會產生出2個鎖。
四、synchronized方法與鎖對象
public class MyObject { synchronized public void methodA(){ try { System.out.println("begin methodA threadName=" + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("end endTime="+System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } public void methodB(){ try { System.out.println("begin methodB threadName=" + Thread.currentThread().getName() + "begin time=" + System.currentTimeMillis()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { MyObject object = new MyObject(); ThreadC c = new ThreadC(object); c.setName("C"); ThreadD d = new ThreadD(object); d.setName("D"); c.start(); d.start(); } } class ThreadC extends Thread{ private MyObject object; public ThreadC(MyObject object){ super(); this.object = object; } @Override public void run(){ super.run(); object.methodA(); } } class ThreadD extends Thread{ private MyObject object; public ThreadD(MyObject object){ super(); this.object = object; } @Override public void run(){ super.run(); object.methodB(); } }
運行結果:
begin methodA threadName=C begin methodB threadName=Dbegin time=1536586015881 end endTime=1536586020882 end
把MyObject中的methodB方法前面加上synchronized
synchronized public void methodB(){ try { System.out.println("begin methodB threadName=" + Thread.currentThread().getName() + "begin time=" + System.currentTimeMillis()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } }
運行結果:
begin methodA threadName=C end endTime=1536586138271 begin methodB threadName=Dbegin time=1536586138272 end
上述兩個運行結果結論:
1)C線程先持有object對象的Lock鎖,D線程可以以異步的方式調用object對象中的非synchronized類型的方法
2)C線程先持有object對象的Lock鎖,D線程如果在這時調用對象中的synchronized類型的方法則需要等待,也就是同步
五、臟讀(dirtyRead)
在賦值的時候進行了同步,但是在取值時有可能出現一些意想不到的狀況,這種情況就會出現臟讀。臟讀這種情況是在讀取實例變量時,此值已經被其他線程更改的過了
public class PublicVar { private String username = "a"; private String password = "aa"; synchronized public void setValue(String username, String password) { try { this.username = username; Thread.sleep(1000); this.password = password; System.out.println("setValue method thread name = " + Thread.currentThread().getName() + ", username=" + username + ",password=" + password); } catch (InterruptedException e) { e.printStackTrace(); } } public void getValue() { System.out.println("getValue method thread name = " + Thread.currentThread().getName() + ", username=" + username + ",password=" + password); } public static void main(String[] args) { try { PublicVar v = new PublicVar(); ThreadAA a = new ThreadAA(v); a.start(); Thread.sleep(100); v.getValue(); } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadAA extends Thread { private PublicVar publicVar; public ThreadAA(PublicVar publicVar) { this.publicVar = publicVar; } @Override public void run() { publicVar.setValue("b", "bb"); } }
運行結果
getValue method thread name = main, username=b,password=aa setValue method thread name = Thread-0, username=b,password=bb
此結果出現了臟讀
接下來把getValue()方法加上同步
synchronized public void getValue() { System.out.println("getValue method thread name = " + Thread.currentThread().getName() + ", username=" + username + ",password=" + password); }
運行結果:
setValue method thread name = Thread-0, username=b,password=bb getValue method thread name = main, username=b,password=bb
未出現臟讀
出現這種狀況就是四中說的synchronized占用鎖是占用的是對象
六、synchronized 鎖重入
關鍵字synchronized擁有鎖重入的功能,也就是在使用synchronized時,當一個線程得到一個對象鎖後,再次請求此對象鎖時可以再次得到該對象的鎖,這也證明了一個synchronized方法/塊的內部調用本類的其他synchronized方法/塊時,是永遠可以得到鎖的,如果不可重入鎖,就會造成死鎖。可重入鎖也支持在父子繼承環境中(存在父子繼承關系時,子類可以通過“可重入鎖”調用父類的同步方法)
七、出現異常、鎖自動釋放
當一個線程執行代碼出現異常時,其所持有的鎖會自動釋放
八、同步不具有繼承性
子類重寫了父類的synchronized方法,但是子類沒有加synchronized,那子類的方法依然是非同步的方法,父類是同步的方法
synchronized同步方法