public class PrintObject { public void printString(){ System.out.println("begin"); if(Thread.currentThread().getName().equals("a")){ PrintStream printStream = System.out; printStream.println("線程 a 運行"); } if(Thread.currentThread().getName().equals("b")){ System.out.println("線程 b 運行"); } System.out.println("end"); } }
public static void main(String[] a) { PrintObject pb = new PrintObject(); Thread thread1 = new Thread(pb::printString); thread1.setName("a"); thread1.start(); Thread thread2 = new Thread(pb::printString); thread2.setName("b"); thread2.start(); }
咱們建立兩個線程來調用同一業務對象的相同功能時, 能夠看到下面輸出.java
begin begin 線程 a 運行 end 線程 b 運行 end
兩個線程在一塊兒執行 printString
方法, 而且交叉打印. 也就是說當咱們啓動一個線程執行某個方法的時候就是異步執行, 至於爲啥要這樣演示, 是由於下面的同步.數據庫
將 synchronized public void printString()
方法上加入 synchronized
關鍵字, 來使方法同步. 安全
執行結果:網絡
begin 線程 a 運行 end begin 線程 b 運行 end
那麼爲何加入 synchronized
關鍵字後就會同步呢? 這是由於關鍵字 synchronized
會取得一把對象鎖, 而不是把一段代碼或方法當作鎖; 哪一個線程先執行帶 synchronized
關鍵字的方法, 哪一個線程就持有該方法所屬的對象的鎖 Look, 那麼其餘線程只能呈等待狀態.異步
這裏有個前提是多個線程訪問同一個對象, 下面演示的是多個線程訪問不一樣的對象.this
public class PrintObject { synchronized public void printString(){ System.out.println("begin"); if(Thread.currentThread().getName().equals("a")){ PrintStream printStream = System.out; printStream.println("線程 a 運行"); try { Thread.sleep(100000); } catch (InterruptedException e) { } } if(Thread.currentThread().getName().equals("b")){ System.out.println("線程 b 運行"); } System.out.println("end"); } }
public static void main(String[] a) { PrintObject pb = new PrintObject(); PrintObject pb1 = new PrintObject(); Thread thread1 = new Thread(pb::printString); thread1.setName("a"); thread1.start(); Thread thread2 = new Thread(pb1::printString); thread2.setName("b"); thread2.start(); }
執行結果線程
begin 線程 a 運行 begin 線程 b 運行 end
讓 a 線程睡眠 100000 毫秒, 能夠看到 a 線程並無執行完, b 線程就運行了. 這也可以證實 synchronized
關鍵字取得是對象鎖.code
另外還須要注意一點, 咱們使用兩個線程執行同一對象的不一樣同步方法時, 若是線程 a 在睡眠, 那麼線程 b 也會一直等待, 線程 a 執行完畢後再去執行.orm
注: 同步方法必定是線程安全的.
若是一個獲取鎖的線程調用其它的synchronized修飾的方法, 會發生什麼?對象
在一個線程使用synchronized方法時調用該對象另外一個synchronized方法, 即一個線程獲得一個對象鎖後再次請求該對象鎖, 是永遠能夠拿到鎖的.
在Java內部, 同一個線程調用本身類中其餘synchronized方法/塊時不會阻礙該線程的執行, 同一個線程對同一個對象鎖是可重入的, 同一個線程能夠獲取同一把鎖屢次, 也就是能夠屢次重入. 緣由是Java中線程得到對象鎖的操做是以線程爲單位的, 而不是以調用爲單位的.
這種狀況也能夠發生在繼承中, 也就是說子類的同步方法調用父類的同步方式時, 時能夠鎖重入的.
可是, 若是子類重寫了父類的方法, 並無使用 synchronized 關鍵字, 則同步就失效了. 由於子類重寫父類的方法, 當咱們調用方法執行代碼時, 執行的是子類的方法, 因此變成了異步執行.
public class PrintObject { public synchronized void printString(){ try { System.out.println(Thread.currentThread().getName() + " 執行"); System.out.println(Thread.currentThread().getName() + " 插入數據到數據庫"); // 讓線程休眠, 模擬出網絡延時 Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + " 共享數據減1"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + " 插入數據到數據庫"); Thread.sleep(5000); if (Thread.currentThread().getName().equals("b")) { SimpleDateFormat df = new SimpleDateFormat("mm:ss"); System.out.println(df.format(new Date())); } } catch (InterruptedException e) { e.printStackTrace(); } } }
PrintObject pb = new PrintObject(); Thread thread1 = new Thread(pb::printString); thread1.setName("a"); Thread thread2 = new Thread(pb::printString); thread2.setName("b"); SimpleDateFormat df = new SimpleDateFormat("mm:ss"); System.out.println(df.format(new Date())); thread1.start(); thread2.start();
執行結果
47:34 a 執行 a 插入數據到數據庫 a 共享數據減1 a 插入數據到數據庫 b 執行 b 插入數據到數據庫 b 共享數據減1 b 插入數據到數據庫 48:04
咱們上面這段程序兩個線程所有執行完所用的時間爲 30 秒, 這裏能夠看出同步方法存在一個很大的弊端.
就是說咱們的某個線程開始執行方法時, 不管咱們操做的是否是共享數據, 別的線程都會等待此線程釋放鎖. 而後繼續執行.
但是咱們在插入數據到數據庫的時候, 並非在操做共享數據, 那麼咱們有沒有什麼辦法, 只同步操做共享數據的那部分代碼呢?
咱們就可使用 synchronized 同步代碼塊, 將程序修改爲下面樣子.
public class PrintObject { public void printString(){ try { System.out.println(Thread.currentThread().getName() + " 執行"); System.out.println(Thread.currentThread().getName() + " 插入數據到數據庫"); // 讓線程休眠, 模擬出網絡延時 Thread.sleep(5000); synchronized(this) { System.out.println(Thread.currentThread().getName() + " 共享數據減1"); Thread.sleep(5000); } System.out.println(Thread.currentThread().getName() + " 插入數據到數據庫"); Thread.sleep(5000); if (Thread.currentThread().getName().equals("b")) { SimpleDateFormat df = new SimpleDateFormat("mm:ss"); System.out.println(df.format(new Date())); } } catch (InterruptedException e) { e.printStackTrace(); } } }
執行結果
54:12 b 執行 a 執行 b 插入數據到數據庫 a 插入數據到數據庫 a 共享數據減1 a 插入數據到數據庫 b 共享數據減1 b 插入數據到數據庫 54:32
減小了10秒的執行時間, 提升了執行效率.
同步方法和同步代碼塊的鎖都是同一把鎖. 同步方法獲取的是該方法的對象鎖, 而同步代碼塊獲取中的參數是 this, 表示當前對象. 因此獲取的是同一把鎖.
synchronized 關鍵字能夠應用在 static 靜態方法上, 表示當前的 *.java 文件對應的 Class 類進行持鎖.
雖然運行結果與 synchronized 關鍵字加到非 static 靜態方法上的結果相似, 可是是對 Class 類進行加鎖, 而 Class 鎖能夠對類的全部對象起做用.
synchronized (DemoApplication.class) { }