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方法是可中斷的