synchronized官方解釋
翻譯成中文:
Synchronized同步方法可以支持使用一種簡單的策略來防止線程干擾和內存一致性錯誤:如果一個對象對多個線程可見,則對該對象變量的所有讀取或寫入都是通過同步方法完成的。
簡單就是說Synchronized的作用就是Java中解決並發問題的一種最常用最簡單的方法 ,他可以確保同一個時刻最多只有一個線程執行同步代碼,從而保證多線程環境下並發安全的效果。 如果有一段代碼被Synchronized所修飾,那么這段代碼就會以原子的方式執行,當多個線程在執行這段代碼的時候,它們是互斥的,不會相互干擾,不會同時執行。
Synchronized工作機制是在多線程環境中使用一把鎖,在第一個線程去執行的時候去獲取這把鎖才能執行,一旦獲取就獨占這把鎖直到執行完畢或者在一定條件下才會去釋放這把鎖,在這把鎖釋放之前其他的線程只能阻塞等待。
synchronized是Java中的關鍵字,被Java原生支持,是一種最基本的同步鎖。
它修飾的對象有以下幾種:
1. 修飾一個代碼塊,被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象。
2. 修飾一個方法,被修飾的方法稱為同步方法,其作用的范圍是整個方法,作用的對象是調用這個方法的對象。
3. 修改一個靜態的方法,其作用的范圍是整個靜態方法,作用的對象是這個類的所有對象。
4. 修改一個類,其作用的范圍是synchronized后面括號括起來的部分,作用主的對象是這個類的所有對象。
不使用並發手段會有什么后果
兩個線程同時i++,最后結果比預計的要少。
下面是測試代碼
public class sync1 implements Runnable{ static sync1 instance = new sync1(); static int i = 0; @Override public void run() { for (int j = 0; j < 100000; j++) { i++; } } public static void main(String[] args) throws InterruptedException {
// 開啟兩個線程 公用同一個instance實例 Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); // 讓線程先等待一會 先輸出i t1.join(); t2.join(); System.out.println(i); } }
輸出結果
每次輸出的結果是不同的,並且不符合我們的預期。
原因:
i++看上去是一個操作,其實包含的是三個操作
1)讀取i。
2)將i加一。
3)將i的值寫入到內存中。
synchronized兩種用法
1. 對象鎖
包括方法鎖(默認鎖對象為this當前實例對象)和同步代碼塊鎖(自己指定鎖對象)。
synchronized(this|object) {}
synchronized(類.class) {}
2. 類鎖
指Synchronized修飾的靜態的方法或指定鎖為Class對象。
對象鎖
代碼塊形式:手動指定鎖對象。
方法鎖形式:Synchronized修飾的普通方法,鎖對象默認為this。
public class synchronizedCode implements Runnable { static synchronizedCode instance = new synchronizedCode(); static int i = 0; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); // 線程還存在 while (t1.isAlive() || t2.isAlive()) { } System.out.println("線程執行結束"); } @Override public void run() { System.out.println("我是對象鎖的代碼塊形式,我叫"+ Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("我是對象鎖的代碼塊形式,我叫"+ Thread.currentThread().getName()+"運行結束"); } }
可以看到同時執行,同時結束。
當使用synchronized包裹要保護的代碼,讓其順序執行。
public class synchronizedCode implements Runnable { static synchronizedCode instance = new synchronizedCode(); static int i = 0; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); // 線程還存在 while (t1.isAlive() || t2.isAlive()) { } System.out.println("線程執行結束"); } @Override public void run() { synchronized (this) { System.out.println("我是對象鎖的代碼塊形式,我叫"+ Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我是對象鎖的代碼塊形式,我叫"+ Thread.currentThread().getName()+"運行結束"); } } }
輸出結果可以看到synchronized保證了代碼塊是竄行執行的。
在上面 synchronized括號里面使用的是this,但有的時候情況會比較復雜,需要自定義一個鎖對象,當業務比較復雜時候,可能需要不止有一個synchronized同步代碼塊,在存在多個synchronized情況下,它們可能需要互相配對。
修改上面的代碼如下,有兩個同步代碼塊,並且使用兩個不同的鎖。
public class synchronizedCode implements Runnable { static synchronizedCode instance = new synchronizedCode(); static int i = 0; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); // 線程還存在 while (t1.isAlive() || t2.isAlive()) { } System.out.println("線程執行結束"); } Object lock1 = new Object(); Object lock2 = new Object(); @Override public void run() { // 第一把鎖 synchronized (lock1) { System.out.println("我是lock1,我叫"+ Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"lock1運行結束"); } // 第二把鎖 synchronized (lock2) { System.out.println("我是lock2,我叫"+ Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"lock2運行結束"); } } }
以下輸出結果,因為存在兩個鎖,所以線程不用等待同一把鎖,在鎖被釋放的時候,兩個線程可以分別同時獲取兩把鎖,當lock2運行結束后,線程Thread-1就可以拿到lock2鎖。
在一般復雜業務場景下,還會出現一個線程等待另外兩個線程,還有線程相互之間通訊等問題。
普通方法鎖
給普通方法加上synchronized修飾符。
public class objectMethod implements Runnable { static objectMethod instance = new objectMethod(); static int i = 0; @Override public void run() { // 調用 try { method(); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void method() throws InterruptedException { System.out.println("該方法是對象鎖的方法修飾符形式, 線程名為:"+ Thread.currentThread().getName()); Thread.sleep(3000); System.out.println(Thread.currentThread().getName()+"運行結束"); } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("運行結束"); } }
運行結果
上面代碼兩個線程調用Method方法,根據結果可知,依然是按照順序執行。
類鎖
我們知道Java類可能會有多個對象,但是只有一個Class對象。
查看線程生命周期
1. 初始(NEW):新創建了一個線程對象,但還沒有調用start()方法。
2. 運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱為“運行”。
線程對象創建后,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取CPU的使用權,此時處於就緒狀態(ready)。就緒狀態的線程在獲得CPU時間片后變為運行中狀態(running)。
3. 阻塞(BLOCKED):表示線程阻塞於鎖。
4. 等待(WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)。
5. 超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間后自行返回。
6. 終止(TERMINATED):表示該線程已經執行完畢。