一、為什麼有線程安全問題存在?
當多個線程同時共享,同一個全局變量或靜态變量,做寫的操作時,可能會發生資料沖突問題,也就是線程安全問題,但是做讀的操作是不會發生資料沖突問題。
二、線程不安全問題怎麼解決?
使用synchronized、jdk1.5并發包lock
三、synchronizd的使用方法
-
使用同步代碼塊
就是将可能發生線程安全問題的代碼,給包裹起來。
//“同一個資料”部分相當于一把鎖,可以使用空的Object對象作為入參傳入
synchronized(同一個資料){
可能會發生線程沖突問題
}
注意:synchronized包裹的部分一定要是可能會發生線程安全問題的代碼,而且synchrozied關鍵字隻适合單個jvm,分布式叢集環境下不能使用,在分布式叢集環境下要使用分布式鎖zookeeper。
package chauncy.threadtrain; class ThreadTrain implements Runnable { // 火車票總數 private int count = 100; //兩個線程一定要用同一把鎖 private Object obj=new Object(); @Override public void run() { while (count > 0) { try { Thread.sleep(40); } catch (InterruptedException e) { } synchronized(obj){ // 100總數減去現有數量count加1為目前出售的第幾張票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票"); count--; } } } } /** * 什麼是線程不安全 * 當多個線程同時操作同一個共享的全局變量,可能會受到其他線程的幹擾。會發生資料沖突的問題。 * 線程不安全問題怎麼解決? * 使用synchronize、jdk1.5的并發包lock * 使用synchronized關鍵字包裹起來的代碼 * 每次隻能讓目前一個線程進行執行 * @classDesc: 功能描述(模拟線程不安全問題) * @author: ChauncyWang * @version: 1.0 */ public class ThreadDemo1 { public static void main(String[] args) { //線程類一定要用一個執行個體,因為要重制變量共享的問題 ThreadTrain threadTrain = new ThreadTrain(); // 1.建立兩個線程 Thread thread1 = new Thread(threadTrain); Thread thread2 = new Thread(threadTrain); thread1.start(); thread2.start(); } }
-
使用同步函數(同步方法)
什麼是同步函數?
在方法上修飾synchronized稱為同步函數。
同步函數用的是什麼鎖?
同步函數使用this鎖。
package chauncy.threadtrain; class ThreadTrain2 implements Runnable { // 火車票總數 private int count = 100; //兩個線程一定要用同一把鎖 private Object obj = new Object(); @Override public void run() { while (count > 0) { show(); } } public synchronized void show() { if (count > 0) { try { Thread.sleep(4); } catch (InterruptedException e) { } // 100總數減去現有數量count加1為目前出售的第幾張票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票"); count--; } } } /** * @classDesc: 功能描述(使用同步函數(同步方法)) * @author: ChauncyWang * @version: 1.0 */ public class ThreadDemo2 { public static void main(String[] args) { //線程類一定要用一個執行個體,因為要重制變量共享的問題 ThreadTrain2 threadTrain2 = new ThreadTrain2(); // 1.建立兩個線程 Thread thread1 = new Thread(threadTrain2); Thread thread2 = new Thread(threadTrain2); thread1.start(); thread2.start(); } }
package chauncy.threadtrain; class ThreadTrain3 implements Runnable { // 火車票總數 private int count = 100; //兩個線程一定要用同一把鎖 private Object obj = new Object(); public boolean flag=true; @Override public void run() { //線程1 flag為true 線程2 flag為false if(flag){ while (count > 0) { //入參為obj情況下線程不安全,為this情況線程安全,證明同步函數使用的是this鎖。 synchronized(this){ if (count > 0) { try { Thread.sleep(4); } catch (InterruptedException e) { } // 100總數減去現有數量count加1為目前出售的第幾張票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票"); count--; } } } }else{ while (count > 0) { show(); } } } public synchronized void show() { if (count > 0) { try { Thread.sleep(4); } catch (InterruptedException e) { } // 100總數減去現有數量count加1為目前出售的第幾張票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票"); count--; } } } /** * @classDesc: 功能描述(證明同步函數是this鎖) * @author: ChauncyWang * @version: 1.0 */ public class ThreadDemo3 { public static void main(String[] args) throws InterruptedException { //線程類一定要用一個執行個體,因為要重制變量共享的問題 ThreadTrain3 threadTrain3 = new ThreadTrain3(); // 1.建立兩個線程 Thread thread1 = new Thread(threadTrain3); Thread thread2 = new Thread(threadTrain3); thread1.start(); Thread.sleep(10); threadTrain3.flag=false; thread2.start(); } }
-
使用靜态同步函數
什麼是靜态同步函數?
方法上加上static關鍵字,并且使用synchronized關鍵字修飾 。
靜态同步函數使用的鎖就不是this鎖,因為this鎖是屬于對象級别的,靜态同步函數應該使用類鎖,即該類的位元組碼檔案。
可以使用getClass方法擷取,也可以使用目前線程類的類名.class 擷取該線程類的位元組碼檔案。
package chauncy.threadtrain; class ThreadTrain4 implements Runnable { // 火車票總數 private static int count = 100; //兩個線程一定要用同一把鎖 private Object obj = new Object(); public boolean flag=true; @Override public void run() { //線程1 flag為true 線程2 flag為false if(flag){ while (count > 0) { //入參為obj情況下線程不安全,為this情況線程安全,證明同步函數使用的是this鎖。 synchronized(ThreadDemo4.class){ if (count > 0) { try { Thread.sleep(4); } catch (InterruptedException e) { } // 100總數減去現有數量count加1為目前出售的第幾張票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票"); count--; } } } }else{ while (count > 0) { show(); } } } /** * static 如果修飾的方法,直接通過類名.方法名進行調用 當class檔案也是位元組碼檔案被加載時,才會被初始化。 static存放在方法區 永久區 * this關鍵字表示目前對象的鎖,static使用類.class的鎖 * @methodDesc: 功能描述() * @author: ChauncyWang * @param: * @returnType: void */ public static synchronized void show() { if (count > 0) { try { Thread.sleep(4); } catch (InterruptedException e) { } // 100總數減去現有數量count加1為目前出售的第幾張票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票"); count--; } } } /** * @classDesc: 功能描述(靜态同步函數) * @author: ChauncyWang * @version: 1.0 */ public class ThreadDemo4 { public static void main(String[] args) throws InterruptedException { //線程類一定要用一個執行個體,因為要重制變量共享的問題 ThreadTrain4 threadTrain4 = new ThreadTrain4(); // 1.建立兩個線程 Thread thread1 = new Thread(threadTrain4); Thread thread2 = new Thread(threadTrain4); thread1.start(); Thread.sleep(10); threadTrain4.flag=false; thread2.start(); } }
四、多線程死鎖
- 死鎖的類型:資料庫死鎖、線程死鎖、行鎖、表鎖等。
-
什麼是多線程死鎖?
在同步中嵌套同步,導緻鎖無法釋放。
-
鎖在什麼時候釋放?
鎖一般在代碼執行完畢之後自動釋放,讓其它線程拿到鎖執行。
-
怎麼避免死鎖?
同步中盡量不要嵌套同步。
- 死鎖的代碼實作:
package chauncy.threadtrain; class ThreadTrain5 implements Runnable { // 火車票總數 private static int count = 100; //兩個線程一定要用同一把鎖 private Object obj = new Object(); public boolean flag=true; @Override public void run() { //線程1 flag為true 線程2 flag為false if(flag){ while (count > 0) { //鎖一般是在代碼執行完畢之後自動釋放,讓其它線程拿到鎖執行 //如果flag為true 先拿到obj這把鎖,再拿到this鎖,才能執行代碼 //如果flag為false 先拿到this這把鎖,再拿到obj鎖,才能執行代碼 synchronized(obj){ show(); } } }else{ while (count > 0) { show(); } } } public synchronized void show() { synchronized(obj){ if (count > 0) { try { Thread.sleep(4); } catch (InterruptedException e) { } // 100總數減去現有數量count加1為目前出售的第幾張票 System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票"); count--; } } } } /** * @classDesc: 功能描述(死鎖實作) * @author: ChauncyWang * @version: 1.0 */ public class ThreadDemo5 { public static void main(String[] args) throws InterruptedException { //線程類一定要用一個執行個體,因為要重制變量共享的問題 ThreadTrain5 threadTrain5 = new ThreadTrain5(); // 1.建立兩個線程 Thread thread1 = new Thread(threadTrain5); Thread thread2 = new Thread(threadTrain5); thread1.start(); Thread.sleep(40); threadTrain5.flag=false; thread2.start(); } }
五、線程間實作同步(線程安全)的問題總結
-
什麼是多線程安全?
多個線程同時共享,同一個全局變量或靜态變量,做寫的操作時,可能會發生資料沖突問題,也就是線程安全問題。做讀操作是不會發生資料沖突問題。
-
如何解決多線程之間線程安全問題?
使用多線程之間同步或使用鎖(lock)。
-
為什麼使用線程同步或使用鎖能解決線程安全問題呢?
将可能會發生資料沖突問題(線程不安全問題),隻能讓目前一個線程進行執行。被包裹的代碼執行完成後釋放鎖,讓後才能讓其他線程進行執行。這樣的話就可以解決線程不安全問題。
-
什麼是多線程之間同步?
當多個線程共享同一個資源,不會受到其他線程的幹擾。
-
什麼是同步代碼塊?
是将可能會發生線程安全問題的代碼,給包括起來。隻能讓目前一個線程進行執行,被包裹的代碼執行完成之後才能釋放所,讓後才能讓其他線程進行執行。
-
同步代碼塊與同步函數差別?
同步代碼使用自定鎖(明鎖),同步函數使用this鎖。
-
同步函數與靜态同步函數差別(一個靜态方法和一個非靜态靜态怎麼實作同步?)?
同步函數使用this鎖,靜态同步函數使用位元組碼檔案,也就是類.class。
-
什麼是多線程死鎖?
同步中嵌套同步,解決辦法:同步中盡量不要嵌套同步。