java並發編程基礎——線程同步
線程同步
一、線程安全問題
如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
線程安全問題往往發生在多個線程調用同一方法或者操作同一變量,但是我們要知道其本質就是CPU對線程的隨機調度,CPU無法保證一個線程執行完其邏輯才去調用另一個線程執行。
package threadtest; public class ThreadTest implements Runnable{ static int i = 0; publicvoid incre() { i++; } @Override public void run() { for(int j=0;j<1000000;j++) { incre(); } } public static void main(String[] args) throws InterruptedException { ThreadTest tt = new ThreadTest(); Thread t1 = newThread(tt); Thread t2 = new Thread(tt); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
由於 i++不是原子操作,先讀取值,再加1 賦值,所以當在讀取i值的時候線程切換了,導致兩個線程讀取的i相同,導致線程安全問題。
1363390 //結果小於2000000
二、線程同步
java多線程支持引入了同步監視器來解決線程同步問題,通過synchronized關鍵字,主要有同步方法和同步代碼塊
執行同步代碼前必須先獲得對同步監視器的鎖定(任何時刻都只有一個線程可以獲得同步監視器的鎖定)
java5開始提供了更強大的同步機制,同步鎖Lock
1、同步方法: synchronized修飾方法
package threadtest; public class ThreadTest implements Runnable{ static int i = 0; public synchronized void incre() { i++; } @Override public void run() { for(int j=0;j<1000000;j++) { incre(); } } public static void main(String[] args) throws InterruptedException { ThreadTest tt = new ThreadTest(); Thread t1 = new Thread(tt); Thread t2 = new Thread(tt); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
同步方法的同步監視器就是方法所屬的對象本身
2000000 //結果正常
synchronized修飾靜態方法
package threadtest; public class ThreadTest implements Runnable{ static int i = 0; /** * 同步靜態方法的同步監視器是該類對應的class對象 */ public static synchronized void incre() { i++; } @Override public void run() { for(int j=0;j<1000000;j++) { incre(); } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new ThreadTest()); Thread t2 = new Thread(new ThreadTest()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
上面類中synchronized修飾的靜態方法,同步監視器是該類對應的class對象,i是類屬性,多個線程調用不同實例,i也是線程安全的。
2000000
2、同步代碼塊
除了使用關鍵字修飾實例方法和靜態方法外,還可以使用同步代碼塊,在某些情況下,我們編寫的方法體可能比較大,同時存在一些比較耗時的操作,而需要同步的代碼又只有一小部分,如果直接對整個方法進行同步操作,可能會得不償失,此時我們可以使用同步代碼塊的方式對需要同步的代碼進行包裹,這樣就無需對整個方法進行同步操作了
package threadtest; public class ThreadTest implements Runnable{ static int i = 0; /** * 同步代碼塊,synchronized(obj),obj就是同步監視器 */ public void incre() { synchronized(this) { i++; } } @Override public void run() { for(int j=0;j<1000000;j++) { incre(); } } public static void main(String[] args) throws InterruptedException { ThreadTest tt = new ThreadTest(); Thread t1 = new Thread(tt); Thread t2 = new Thread(tt); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
同步代碼塊修飾靜態方法
package threadtest; public class ThreadTest implements Runnable{ static int i = 0; /** * 同步代碼塊,synchronized(obj),obj就是同步監視器 */ public static void incre() { synchronized(ThreadTest.class) { i++; } } @Override public void run() { for(int j=0;j<1000000;j++) { incre(); } } public static void main(String[] args) throws InterruptedException { //ThreadTest tt = new ThreadTest(); Thread t1 = new Thread(new ThreadTest()); Thread t2 = new Thread(new ThreadTest()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
3、同步鎖Lock
java5開始提供了通過顯示定義同步鎖對象來實現同步。
在實現線程安全中,比較常用的是ReentrantLock(可重入鎖),它是Lock接口的實現類。
package threadtest; import java.util.concurrent.locks.ReentrantLock; public class ThreadTest implements Runnable{ private final ReentrantLock lock = new ReentrantLock(); static int i = 0; public void incre() { lock.lock();//加鎖 try { i++; } finally { lock.unlock(); } } @Override public void run() { for(int j=0;j<1000000;j++) { incre(); } } public static void main(String[] args) throws InterruptedException { ThreadTest tt = new ThreadTest(); Thread t1 = new Thread(tt); Thread t2 = new Thread(tt); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
2000000//線程安全
4、死鎖
當兩個線程同時等待對方釋放同步監視器就會發生死鎖,java虛擬機沒有檢測,也沒有采取措施來處理死鎖情況,所以多線程編程時應該采取措施避免死鎖出現。
一旦出現死鎖,程序不會發生任何異常情況,也沒有任何提示,只是所有線程處於阻塞狀態,無法繼續
下面程序就是發生死鎖
package threadtest; /** * 一個簡單的死鎖例子,大概的思路:兩個線程A和B,兩把鎖X和Y,現在A先拿到鎖X,然後sleep()一段時間,我們知道sleep()是不會釋放鎖資源的。然後如果這段時間線程B拿到鎖Y,也sleep()一段時間的話,那麽等到兩個線程都醒過來的話,那麽將互相等待對方釋放鎖資源而僵持下去,陷入死鎖。flag的作用就是讓A和B獲得不同的鎖。 * @author rdb * */ public class ThreadTest implements Runnable{ Object o1 = new Object(); Object o2 = new Object(); private boolean flag = true ; @Override public void run() { if(flag) { flag = false; synchronized (o1) { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o2) { System.out.println("**************"); } } }else { flag = true; synchronized (o2) { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o1) { System.out.println("**************"); } } } } public static void main(String[] args) throws InterruptedException { ThreadTest tt = new ThreadTest(); Thread t1 = new Thread(tt); Thread t2 = new Thread(tt); t1.start(); t2.start(); } }
java並發編程基礎——線程同步