java ---執行緒池和多執行緒
阿新 • • 發佈:2020-08-05
執行緒池
執行緒池,其實就是一個容納多個執行緒的容器,其中的執行緒可以反覆使用,省去了頻繁建立執行緒物件的操作,無需反覆建立執行緒而消耗過多資源。
使用執行緒池方式--Runnable介面
使用執行緒池中執行緒物件的步驟:
1. 建立執行緒池物件
2. 建立Runnable介面子類物件
3. 提交Runnable介面子類物件
4. 關閉執行緒池
// 程式碼演示: public class ThreadPoolDemo { public static void main(String[] args) { //建立執行緒池物件 ExecutorService service = Executors.newFixedThreadPool(2);//包含2個執行緒物件 //建立Runnable例項物件 MyRunnable r = new MyRunnable(); //自己建立執行緒物件的方式 //Thread t = new Thread(r); //t.start(); ---> 呼叫MyRunnable中的run() //從執行緒池中獲取執行緒物件,然後呼叫MyRunnable中的run() service.submit(r); //再獲取個執行緒物件,呼叫MyRunnable中的run()service.submit(r); service.submit(r); //注意:submit方法呼叫結束後,程式並不終止,是因為執行緒池控制了執行緒的關閉。將使用完的執行緒又歸還到了執行緒池中 //關閉執行緒池 //service.shutdown(); } } // Runnable介面實現類 public class MyRunnable implements Runnable { @Override public void run() { System.out.println("我要一個教練");try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("教練來了: " +Thread.currentThread().getName()); System.out.println("教我游泳,交完後,教練回到了游泳池"); } }
使用執行緒池方式—Callable介面
使用執行緒池中執行緒物件的步驟:
1. 建立執行緒池物件
2. 建立Callable介面子類物件
3. 提交Callable介面子類物件
4. 關閉執行緒池
//程式碼演示: public class ThreadPoolDemo { public static void main(String[] args) { //建立執行緒池物件 ExecutorService service = Executors.newFixedThreadPool(2);//包含2個執行緒物件 //建立Callable物件 MyCallable c = new MyCallable(); //從執行緒池中獲取執行緒物件,然後呼叫MyRunnable中的run() service.submit(c); //再獲取個教練 service.submit(c); service.submit(c); //注意:submit方法呼叫結束後,程式並不終止,是因為執行緒池控制了執行緒的關閉。將使用完的執行緒又歸還到了執行緒池中 //關閉執行緒池 //service.shutdown(); } } // Callable介面實現類,call方法可丟擲異常、返回執行緒任務執行完畢後的結果 public class MyCallable implements Callable { @Override public Object call() throws Exception { System.out.println("我要一個教練:call"); Thread.sleep(2000); System.out.println("教練來了: " +Thread.currentThread().getName()); System.out.println("教我游泳,交完後,教練回到了游泳池"); return null; } }
執行緒池練習:返回兩個數相加的結果
要求:通過執行緒池中的執行緒物件,使用Callable介面完成兩個數求和操作
Future介面:用來記錄執行緒任務執行完畢後產生的結果。執行緒池建立與使用
get() 獲取Future物件中封裝的資料結果
//程式碼演示: public class ThreadPoolDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { //建立執行緒池物件 ExecutorService threadPool = Executors.newFixedThreadPool(2); //建立一個Callable介面子類物件 //MyCallable c = new MyCallable(); MyCallable c = new MyCallable(100, 200); MyCallable c2 = new MyCallable(10, 20); //獲取執行緒池中的執行緒,呼叫Callable介面子類物件中的call()方法, 完成求和操作 //<Integer> Future<Integer> submit(Callable<Integer> task) // Future 結果物件 Future<Integer> result = threadPool.submit(c); //此 Future 的 get 方法所返回的結果型別 Integer sum = result.get(); System.out.println("sum=" + sum); //再演示 result = threadPool.submit(c2); sum = result.get(); System.out.println("sum=" + sum); //關閉執行緒池(可以不關閉) } } // Callable介面實現類 public class MyCallable implements Callable<Integer> { //成員變數 int x = 5; int y = 3; //構造方法 public MyCallable(){ } public MyCallable(int x, int y){ this.x = x; this.y = y; } @Override public Integer call() throws Exception { return x+y; } }
多執行緒
執行緒安全
我們通過一個案例,演示執行緒的安全問題:
電影院要賣票,我們模擬電影院的賣票過程。假設要播放的電影是 “功夫熊貓3”,本次電影的座位共100個(本場電影只能賣100張票)。
我們來模擬電影院的售票視窗,實現多個視窗同時賣 “功夫熊貓3”這場電影票(多個視窗一起賣這100張票)
需要視窗,採用執行緒物件來模擬;需要票,Runnable介面子類來模擬
// 測試類 public class ThreadDemo { public static void main(String[] args) { //建立票物件 Ticket ticket = new Ticket(); //建立3個視窗 Thread t1 = new Thread(ticket, "視窗1"); Thread t2 = new Thread(ticket, "視窗2"); Thread t3 = new Thread(ticket, "視窗3"); t1.start(); t2.start(); t3.start(); } } // 模擬票 public class Ticket implements Runnable { //共100票 int ticket = 100; @Override public void run() { //模擬賣票 while(true){ if (ticket > 0) { //模擬選坐的操作 try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--); } } }
同步程式碼塊
同步程式碼塊: 在程式碼塊宣告上 加上synchronized
synchronized (鎖物件) { 可能會產生執行緒安全問題的程式碼 } //同步程式碼塊中的鎖物件可以是任意的物件;但多個執行緒時,要使用同一個鎖物件才能夠保證執行緒安全。
使用同步程式碼塊,對電影院賣票案例中Ticket類進行如下程式碼修改:
public class Ticket implements Runnable { //共100票 int ticket = 100; //定義鎖物件 Object lock = new Object(); @Override public void run() { //模擬賣票 while(true){ //同步程式碼塊 synchronized (lock){ if (ticket > 0) { //模擬電影選坐的操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--); } } } } }
同步方法
同步方法:在方法宣告上加上synchronized
public synchronized void method(){ 可能會產生執行緒安全問題的程式碼 } //同步方法中的鎖物件是 this
使用同步方法,對電影院賣票案例中Ticket類進行如下程式碼修改:
public class Ticket implements Runnable { //共100票 int ticket = 100; //定義鎖物件 Object lock = new Object(); @Override public void run() { //模擬賣票 while(true){ //同步方法 method(); } } //同步方法,鎖物件this public synchronized void method(){ if (ticket > 0) { //模擬選坐的操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--); } } }
Lock介面
我們使用Lock介面,以及其中的lock()方法和unlock()方法替代同步,對電影院賣票案例中Ticket類進行如下程式碼修改:
public class Ticket implements Runnable { //共100票 int ticket = 100; //建立Lock鎖物件 Lock ck = new ReentrantLock(); @Override public void run() { //模擬賣票 while(true){ //synchronized (lock){ ck.lock(); if (ticket > 0) { //模擬選坐的操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--); } ck.unlock(); //} } } }