從零開始學Java-Day17
阿新 • • 發佈:2021-06-23
多執行緒程式設計的兩種實現方式
- extends Thread
- 優點:
- 缺點:後續變化小,侷限性大
- implement Runnable
- 優點:多實現,更加靈活且解耦
- 缺點:寫法相對複雜,一些資源需要藉助Thread
多執行緒資料安全隱患
- 怎麼產生?執行緒的隨機性+訪問延遲
- 以後如何判斷程式有沒有執行緒安全問題
在多執行緒程式中 + 有共享資料 + 多條語句操作共享資料
- 單執行緒程式不會出現多執行緒搶佔資源的情況
- 如果沒有共享資料,互不干涉,也不會出現資料安全問題
- 多條語句操作了共享資料,而多條語句執行是需要時間的,存在延遲所以這個時間差導致了資料的安全問題
package cn.tedu.tickets; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestRunnableV2 { public static void main(String[] args) { TicketRunnable target = new TicketRunnable(); // Thread t1 = new Thread(target, "黃金船"); // Thread t2 = new Thread(target, "目白麥昆"); // Thread t3 = new Thread(target, "東海帝王"); // Thread t4 = new Thread(target, "小慄帽"); // t1.start(); // t2.start(); // t3.start(); // t4.start(); //執行緒池ExecutorService:用於儲存執行緒的池子,把新建/啟動/關閉執行緒都交給池來做 //Executors:用於建立執行緒池的工具類,newFixedThreadPool() ExecutorService pool = Executors.newFixedThreadPool(5); for (int i = 1; i <= 5; i++){ pool.execute(target); } pool.shutdown(); } } class TicketRunnable implements Runnable{ static int tickets = 100; Object object = new Object(); @Override public void run() { while (true){ /*鎖物件必須唯一*/ /* 如果一個方法中所有程式碼均需要被同步,那麼可以用 synchronized 修飾 被synchronized關鍵字修飾的方法稱為同步方法 */ synchronized (object){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if (tickets > 0){ System.out.println(Thread.currentThread().getName() + "賣了第" + tickets-- + "張票" ); } if (tickets <= 0){ break; } } } } }
多執行緒安全問題解決
加鎖:注意
- 加鎖的位置
同步|亦步
同步:類似於排隊效果
- 優點:執行緒安全
- 缺點:效率低
非同步:不排隊,多執行緒效果,各執行緒都搶佔資源
- 優點:效率高
- 缺點:執行緒不安全
雙重校驗機制
-
增加判斷控制--有票的時候在賣票
-
同步程式碼塊,主要強調同步,同一時刻同一資源只能被一個執行緒物件
-
sychronized(鎖物件){可能會發生安全問題的程式碼}
- 同步程式碼塊使用的鎖物件必須唯一
package cn.tedu.tickets; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestRunnableV2 { public static void main(String[] args) { TicketRunnable target = new TicketRunnable(); // Thread t1 = new Thread(target, "黃金船"); // Thread t2 = new Thread(target, "目白麥昆"); // Thread t3 = new Thread(target, "東海帝王"); // Thread t4 = new Thread(target, "小慄帽"); // t1.start(); // t2.start(); // t3.start(); // t4.start(); //執行緒池ExecutorService:用於儲存執行緒的池子,把新建/啟動/關閉執行緒都交給池來做 //Executors:用於建立執行緒池的工具類,newFixedThreadPool() ExecutorService pool = Executors.newFixedThreadPool(5); for (int i = 1; i <= 5; i++){ pool.execute(target); } pool.shutdown(); } } class TicketRunnable implements Runnable{ static int tickets = 100; Object object = new Object(); @Override public void run() { while (true){ /*鎖物件必須唯一*/ /* 如果一個方法中所有程式碼均需要被同步,那麼可以用 synchronized 修飾 被synchronized關鍵字修飾的方法稱為同步方法 */ synchronized (object){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if (tickets > 0){ System.out.println(Thread.currentThread().getName() + "賣了第" + tickets-- + "張票" ); } if (tickets <= 0){ break; } } } } }
package cn.tedu.tickets; //本類用於解決繼承下多執行緒售票案例的資料安全問題 public class TestExtendV2 { public static void main(String[] args) { TicketThreadV2 t1 = new TicketThreadV2("小林"); TicketThreadV2 t2 = new TicketThreadV2("托爾"); TicketThreadV2 t3 = new TicketThreadV2("法夫涅爾"); TicketThreadV2 t4 = new TicketThreadV2("康納"); // t1.setName("小林"); // t2.setName("托爾"); // t3.setName("法夫涅爾"); // t4.setName("康納"); t1.start(); t2.start(); t3.start(); t4.start(); } } class TicketThreadV2 extends Thread{ private static int tickets = 100; public TicketThreadV2() { } public TicketThreadV2(String name) { setName(name); } @Override public void run() { while (true){ /* * 雙重校驗二:使用同步程式碼塊 * synchronized(鎖物件){容易發生資料安全問題的程式碼} * 在同步程式碼塊中,同一時刻,同一資源只能被一個執行緒獨享,排隊 * 注意:鎖物件必須唯一,如果不唯一,還會發生安全問題 * 如果是繼承的方式,鎖物件一般使用本類的位元組碼物件 */ synchronized (TicketThreadV2.class){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if (tickets <= 0){ break; } if (tickets == 1){ System.out.println("票賣完了"); } //雙重校驗一 if (tickets > 0){ System.out.println(getName() + "買到第" + tickets-- + "張票"); } } } } }
ExecutorService/Executors
ExecutorService:用來儲存執行緒的池子,把新建執行緒/啟動執行緒/關閉執行緒的任務都交給池來管理
- execute(Runnable任務物件) 把任務丟到執行緒池
Executors 輔助建立執行緒池的工具類
- newFixedThreadPool(int nThreads) 最多n個執行緒的執行緒池
- newCachedThreadPool() 足夠多的執行緒,使任務不必等待
- newSingleThreadExecutor() 只有一個執行緒的執行緒池
執行緒池
ExecutorService:用來儲存執行緒的池子,把新建執行緒/啟動執行緒/關閉執行緒的任務都交給池來管理
建立執行緒池的工具類:Executors.newFixedThreadPool(int 自定義執行緒數)
啟動執行緒池中的執行緒:pool.execute(target目標業務物件)
執行緒池會自動管理執行緒,執行緒池目前單機測試不關閉,需要手動關閉
執行緒鎖
悲觀鎖(sychronized):也叫互斥鎖
樂觀鎖
設計模式
單例模式可以說是大多數開發人員在實際中使用最多的,常見的Spring預設建立的bean就是單例模式的。
單例模式有很多好處,比如可節約系統記憶體空間,控制資源的使用。
其中單例模式最重要的是確保物件只有一個。
簡單來說,保證一個類在記憶體中的物件就一個。
單例設計模式:
- 建立一個私有化靜態方法
- 私有化的構造方法不讓外部呼叫
- 通過自定義的靜態方法獲取例項
實現思路:
- 構造方法私有化--為了防止外部直接呼叫本類構造方法
- 本類呼叫構造方法建立私有物件--物件私有化是為了不讓外界直接獲取
- 提供公共的全域性訪問點--為了讓外界按指定的方式獲取物件
package cn.likou.demo06;
public class Test02 {
private Test02(){}
static private Test02 t = new Test02();
public static Test02 go(){
return t;
}
public static void main(String[] args) {
Test02 t1 = Test02.go();
Test02 t2 = Test02.go();
System.out.println(t1);
System.out.println(t2);
System.out.println(t1 == t2);
MySingle s1 = MySingle.go();
MySingle s2 = MySingle.go();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}
}
class MySingle{
private MySingle(){}
static private MySingle mySingle;
public static MySingle go(){
if (mySingle == null){
mySingle = new MySingle();
}
return mySingle;
}
}