在前兩節的《Java多線程學習(一)——多線程基礎》和《Java多線程學習(二)——Thread類的方法介紹》中咱們接觸了線程安全和非線程安全的概念,這節就來學習一下synchronized關鍵字的使用。java
「非線程安全」問題存在於「實例變量中」,若是是方法內部私有的變量,則不存在「非線程安全」的問題。若是兩個線程同時操做對象中的實例變量,會出現非線程安全的問題,解決方法是在方法上添加添加synchronized關鍵字控制同步。git
先看代碼:github
public class HasSelfPrivateNum {
// 建立被同步關鍵字修飾的方法
private int num = 0;
synchronized public void add(String name){
try {
if ("a".equals(name)){
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
}else {
num = 200;
System.out.println("b set over");
}
System.out.println(name + " num = " + num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
複製代碼
public class ThreadA extends Thread {
// 建立線程,構造器傳參爲上面方法的對象
private HasSelfPrivateNum hasSelfPrivateNum;
public ThreadA(String name, HasSelfPrivateNum hasSelfPrivateNum){
super();
this.hasSelfPrivateNum = hasSelfPrivateNum;
this.setName(name);
}
@Override
public void run() {
super.run();
hasSelfPrivateNum.add(this.getName());
}
}
複製代碼
// main方法和輸出
public static void main(String[] args){
HasSelfPrivateNum hasSelfPrivateNum = new HasSelfPrivateNum();
HasSelfPrivateNum hasSelfPrivateNum1 = new HasSelfPrivateNum();
ThreadA threadA = new ThreadA("a", hasSelfPrivateNum);
ThreadA threadA1 = new ThreadA("b", hasSelfPrivateNum1);
threadA.start();
threadA1.start();
}
b set over
a set over
b num = 200
a num = 100
複製代碼
因爲本實例建立了兩個HasSelfPrivateNum對象,因此就會產生兩個鎖,因此運行的結果是異步的。安全
synchronized取得的鎖都是對象鎖,而不是把一段代碼或方法當作鎖。因此在上面的實例中,哪一個線程先執行帶synchronized關鍵字的方法,則哪一個線程就持有該方法所屬對象的鎖Lock,那麼其餘線程只能呈等待狀態,前提是多個線程訪問的是同一個對象。可是若是多個線程訪問多個對象,JVM會建立多個鎖。微信
將main方向修改一下:多線程
public static void main(String[] args){
HasSelfPrivateNum hasSelfPrivateNum = new HasSelfPrivateNum();
//HasSelfPrivateNum hasSelfPrivateNum1 = new HasSelfPrivateNum();
ThreadA threadA = new ThreadA("a", hasSelfPrivateNum);
ThreadA threadA1 = new ThreadA("b", hasSelfPrivateNum);
threadA.start();
threadA1.start();
}
----
a set over
a num = 100
b set over
b num = 200
複製代碼
能夠看到threadA和threadA1拿到的是一個對象的鎖,因此是順序輸出的。異步
經過上面咱們知道synchronized取得的鎖都是對象鎖,而不是把一段代碼或方法當作鎖。若是多個線程訪問的是同一個對象,哪一個線程先執行帶synchronized關鍵字的方法,則哪一個線程就持有該方法,那麼其餘線程只能呈等待狀態。若是多個線程訪問的是多個對象則不必定,由於多個對象會產生多個鎖。ide
若是多個線程訪問的是同一個對象中的未被synchronized關鍵字修飾的方法,線程會異步調用未被修飾的方法。學習
在賦值的時候進行了同步,但在取值的時候可能會出現一些意想不到的意外,這種狀況就是髒讀。發生髒讀的狀況是在讀取實例變量的時候,此值已經被其餘線程修改。測試
public class DirtyReadTest {
// 建立同步修改數據方法,非同步取數據方法
public String username = "A";
public String password = "AA";
synchronized public void setValue(String username, String password){
try {
this.username = username;
Thread.sleep(5000);
this.password = password;
System.out.println("setValue method: username = " + username + " password = " + password);
}catch (InterruptedException e){
e.printStackTrace();
}
}
public void getValue(){
System.out.println("getValue method: username = " + username + " password = " + password);
}
}
複製代碼
public class ThreadTest extends Thread {
private DirtyReadTest dirtyReadTest;
public ThreadTest(DirtyReadTest dirtyReadTest){
super();
this.dirtyReadTest=dirtyReadTest;
}
@Override
public void run() {
super.run();
dirtyReadTest.setValue("B", "BB");
}
}
複製代碼
輸出:
public static void main(String[] args) throws Exception{
DirtyReadTest dirtyReadTest = new DirtyReadTest();
ThreadTest threadTest = new ThreadTest(dirtyReadTest);
threadTest.start();
Thread.sleep(200);
dirtyReadTest.getValue();
}
getValue method: username = B password = AA
setValue method: username = B password = BB
複製代碼
解決方法是在getValue方法上加上synchronized關鍵字。
當線程1調用一個對象的synchronized方法A時候,線程1就會獲取該對象的鎖,這是線程2是沒法獲取該對象的被synchronized修飾的任何方法,可是能夠訪問未被synchronized修飾的方法。
在使用synchronized時,當一個線程獲得一個對象的鎖後,沒有釋放,再次請求該對象的鎖是能夠再次獲取到的。一個對象中的synchronized修飾的方法能夠訪問該對象中其餘被synchronized修飾的方法,若是不可重入鎖會形成死鎖。
測試方法:
public class Service {
synchronized public void service1(){
System.out.println("service1");
service2();
}
synchronized public void service2(){
System.out.println("service2");
service3();
}
synchronized public void service3(){
System.out.println("service3");
}
}
複製代碼
public class ThreadTest extends Thread {
@Override
public void run() {
super.run();
Service service = new Service();
service.service1();
}
}
複製代碼
public static void main(String[] args){
ThreadTest threadTest = new ThreadTest();
threadTest.start();
}
service1
service2
service3
複製代碼
可重入鎖也支持在父子類繼承的環境中,子類能夠經過可重入鎖調用父類的同步方法。這裏再也不寫代碼了,能夠自行寫一寫。
當一個線程執行的代碼出現異常時,其全部的鎖會自動釋放。
若是父類有一個帶synchronized關鍵字的方法,子類繼承並重寫了這個方法。 可是同步不能繼承,因此仍是須要在子類方法中添加synchronized關鍵字。
本節代碼的GitHub。
歡迎關注公衆號: