鎖:synchronized(synchronized保證三大特性、synchronized的特性)
1、synchronized保證三大特性
原子性
(1)使用synchronized保證原子性
在第一個執行緒獲取到鎖之後,在他執行完之前不允許其他的執行緒獲取鎖並操作共享資料,從而保證了程式的原子性。synchronized保證原子性的原理,synchronized保證只有一個執行緒拿到鎖,能夠進入同步程式碼塊
可見性
(1)volatile關鍵字
(2)使用synchronized
(3)列印(因為列印語句裡面也有用到synchronized)
有序性
(1)為什麼要重排序
為了提高程式的執行效率,編譯器和CPU會對程式中程式碼進行重排序。
(2)as-if-serial語義
as-if-serial語義的意思是:不管編譯器和CPU如何重排序,必須保證在單執行緒情況下程式的結果是正確的。
編譯器和處理器不會對存在資料依賴關係的操作做重排序,因為這種重排序會改變執行結果。但是,如果操作之間不存在資料依賴關係,這些操作就可能被編譯器和處理器重排序。例如:修改順序後運算結果改變
(3)synchronized 保證有序性的原理
synchronized後,雖然進行了重排序,保證只有一個執行緒會進入同步程式碼塊,也能保證有序性。
synchronized保證有序性的原理,我們加synchronized後,依然會發生重排序,只不過,我們有同步程式碼塊,可以保證只有一個執行緒執行同步程式碼中的程式碼。保證有序性
2、synchronized的特性
可重入
(1)可重入演示
public class Test { public static void main(String[] args) { Runnable sellTicket = new Runnable() { @Override public void run() { synchronized (Test.class) { System.out.println("我是run"); test01(); } }public void test01() { synchronized (Test.class) { System.out.println("Test"); } } }; new Thread(sellTicket).start(); new Thread(sellTicket).start(); } }
我是run
Test
我是run
Test
概念:
一個執行緒可以多次執行synchronized,重複獲取同一把鎖。
原理:
synchronized的鎖物件中有一個計數器(recursions變數)會記錄執行緒獲得幾次鎖
synchronized是可重入鎖,內部鎖物件中會有一個計數器記錄執行緒獲取幾次鎖了,在執行完同步程式碼塊時,計數器的數量會-1,直到計數器的數量為0,就釋放這個鎖
好處:
- 可以避免死鎖
- 可以讓我們更好的來封裝程式碼
不可中斷
(1)synchronized 不可中斷的演示
public class Test { private static Object obj = new Object(); public static void main(String[] args) throws InterruptedException { Runnable run = () -> { synchronized (obj) { //在Runnable定義同步程式碼塊 String name = Thread.currentThread().getName(); System.out.println(name + "進入同步程式碼塊"); try { // 保證不退出同步程式碼塊 Thread.sleep(888888); } catch (InterruptedException e) { e.printStackTrace(); } } }; Thread t1 = new Thread(run); // 開啟一個執行緒來執行同步程式碼塊 t1.start(); Thread.sleep(1000);//主執行緒 Thread t2 = new Thread(run); //後開啟一個執行緒來執行同步程式碼塊(阻塞狀態) t2.start();//沒有鎖阻塞 System.out.println("停止執行緒前"); // 停止第二個執行緒 t2.interrupt();//中斷執行緒 System.out.println("停止執行緒後"); System.out.println(t1.getState());//執行的是sleep,處於TIMED_WAITING System.out.println(t2.getState()); } }
Thread-0進入同步程式碼塊
停止執行緒前
停止執行緒後
TIMED_WAITING
BLOCKED
一個執行緒獲得鎖後,另一個執行緒想要獲得鎖,必須處於阻塞或等待狀態,如果第一個執行緒不釋放鎖,第二個執行緒會一直阻塞或等待,不可被中斷。
(2)ReentrantLock不可中斷的演示
public class Test { private static Lock lock = new ReentrantLock(); public static void test01() throws InterruptedException { Runnable run = () -> { String name = Thread.currentThread().getName(); try { lock.lock(); System.out.println(name + "獲得鎖,進入鎖執行"); Thread.sleep(88888); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println(name + "釋放鎖"); } }; Thread t1 = new Thread(run);//第一個執行緒 t1.start(); Thread.sleep(1000); Thread t2 = new Thread(run);//第二個執行緒 t2.start(); System.out.println("停止t2執行緒前"); t2.interrupt();//終止執行緒2 System.out.println("停止t2執行緒後"); Thread.sleep(1000); System.out.println(t1.getState());//TIMED_WAITING //一個執行緒在一個特定的等待時間內等待另一個執行緒完成一個動作會在這個狀態 System.out.println(t2.getState());//WAITING // 一個執行緒在等待另一個執行緒執行一個動作時在這個狀態 } public static void main(String[] args) throws InterruptedException { test01(); } }
但是Lock也存在可中斷的情況:
public class Test { private static Lock lock = new ReentrantLock(); public static void test02() throws InterruptedException { Runnable run = () -> { String name = Thread.currentThread().getName(); boolean b = false; try { b = lock.tryLock(3, TimeUnit.SECONDS); if (b) { System.out.println(name + "獲得鎖,進入鎖執行"); Thread.sleep(88888); } else { System.out.println(name + "在指定時間沒有得到鎖做其他操作"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (b) { lock.unlock(); System.out.println(name + "釋放鎖"); } } }; Thread t1 = new Thread(run); t1.start(); Thread.sleep(1000); Thread t2 = new Thread(run);//第二個執行緒嘗試獲得鎖,停留三秒後未獲得成功,執行false後的語句 t2.start(); } public static void main(String[] args) throws InterruptedException { test02(); } }
Thread-0獲得鎖,進入鎖執行
Thread-1在指定時間沒有得到鎖做其他操作
不可中斷是指,當一個執行緒獲得鎖後,另一個執行緒一直處於阻塞或等待狀態,前一個執行緒不釋放鎖,後一個執行緒會一直阻塞或等待,不可被中斷。
- synchronized屬於不可被中斷
- Lock的lock方法是不可中斷的
- Lock的tryLock方法是可中斷的