線程池
線程池,其實就是一個容納多個線程的容器,其中的線程可以反復使用,省去了頻繁創建線程對象的操作,無需反復創建線程而消耗過多資源。
除了創建和銷毀線程的開銷之外,活動的線程也需要消耗系統資源。線程池主要用來解決線程生命周期開銷問題和資源不足問題。
使用線程池方式--Runnable接口
通常,線程池都是通過線程池工廠創建,再調用線程池中的方法獲取線程,再通過線程去執行任務方法。
Executors:線程池創建工廠類 public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象 ExecutorService:線程池類 Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行 Future接口:用來記錄線程任務執行完畢后產生的結果。線程池創建與使用
使用線程池中線程對象的步驟:
1. 創建線程池對象
2. 創建Runnable接口子類對象
3. 提交Runnable接口子類對象
4. 關閉線程池
package com.oracle.xiancheng; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Xiancheng { public static void main(String[] args) { //創建線程池對象(從線程池工廠中獲得) ExecutorService ec=Executors.newFixedThreadPool(2); //創建Runnable接口子類實例對象(創建任務對象) MyRunnable r = new MyRunnable(); // 提交Runnable接口子類對象 //線程池會選一條線程執行提交的任務 ec.submit(r); //注意:submit方法調用結束后,程序並不終止,是因為線程池控制了線程的關閉。 //又將使用完的線程又歸還到了線程池中 for(int i=0;i<50;i++){ System.out.println("main..."+i); } ec.shutdown();//關閉線程池 } }
Runnable接口實現類
package com.oracle.xiancheng; public class MyRunnable implements Runnable { public void run() { for(int i=0;i<50;i++){ System.out.println("runable..."+i); } } }
使用線程池方式—Callable接口
Callable接口:與Runnable接口功能相似,用來指定線程的任務。其中的call()方法,用來返回線程任務執行完畢后的結果,call方法可拋出異常。 ExecutorService:線程池類 <T> Future<T> submit(Callable<T> task):獲取線程池中的某一個線程對象,並執行線程中的call()方法 Future接口:用來記錄線程任務執行完畢后產生的結果。線程池創建與使用
使用線程池中線程對象的步驟:
1. 創建線程池對象
2. 創建Callable接口子類對象
3. 提交Callable接口子類對象
4. 關閉線程池
Callable接口實現類,call方法可拋出異常、返回線程任務執行完畢后的結果
package com.oracle.xiancheng; import java.util.concurrent.Callable; //Callable泛型就是你call的返回值類型 public class MyCallable implements Callable<String> { public String call() throws Exception { return "這是call方法"; } }
測試類:
package com.oracle.xiancheng; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class demo01 { public static void main(String[] args) throws InterruptedException, ExecutionException { //獲取線程池 ExecutorService ec=Executors.newFixedThreadPool(2); //創建callable子類 MyCallable mc=new MyCallable(); //提交Callable子類 Future<String> f=ec.submit(mc); String str=f.get(); System.out.println(str); //關閉線程池 ec.shutdown(); } }
異步計算0-100的和跟0-200的和
package com.oracle.xiancheng; import java.util.concurrent.Callable; public class JiSuan implements Callable<Integer>{ private Integer a; public JiSuan(Integer a){ this.a=a; } public Integer call(){ int sum=0; for(int i=0;i<=a;i++){ sum +=i; } System.out.println(sum); return sum; } }
測試類:
package com.oracle.xiancheng; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Yibu { //異步計算0-100的和跟0-200的和 public static void main(String[] args) throws InterruptedException, ExecutionException { //獲取線程池 ExecutorService ec=Executors.newFixedThreadPool(2); //創建callable子類 JiSuan js=new JiSuan(100); JiSuan js1=new JiSuan(200); //提交callable子類 Future<Integer> f1=ec.submit(js); Future<Integer> f2=ec.submit(js1); /*//從future中獲取返回值 System.out.println(f1.get()); System.out.println(f2.get());*/ //關閉 ec.shutdown(); } }
多線程
線程安全
電影院要賣票,我們模擬電影院的賣票過程。假設要播放的電影是 “功夫熊貓3”,本次電影的座位共100個(本場電影只能賣100張票)。
我們來模擬電影院的售票窗口,實現多個窗口同時賣 “功夫熊貓3”這場電影票(多個窗口一起賣這100張票)
需要窗口,采用線程對象來模擬;需要票,Runnable接口子類來模擬
模擬票:
public class Ticket implements Runnable { private int tickets=100; public void run(){
//模擬賣票 while(true){ if(tickets>0){
//模擬選座 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+tickets--+"張票"); } } } }
測試類;
package com.oracle.demo01; public class demo01 { public static void main(String[] args) { Ticket t=new Ticket();//創建票對象 Thread t1=new Thread(t); Thread t2=new Thread(t); Thread t3=new Thread(t); t1.start(); t2.start(); t3.start(); } }
結果會出現重復的票,還有錯誤的票,0和-1問題
線程同步(線程安全處理Synchronized)
java中提供了線程同步機制,它能夠解決上述的線程安全問題。
線程同步的方式有兩種:
方式1:同步代碼塊
方式2:同步方法
同步代碼塊
//同步代碼塊: 在代碼塊聲明上 加上synchronized synchronized (鎖對象) { //可能會產生線程安全問題的代碼 }
同步代碼塊中的鎖對象可以是任意的對象;但多個線程時,要使用同一個鎖對象才能夠保證線程安全。
使用同步代碼塊,對電影院賣票案例中Ticket類進行如下代碼修改:
模擬出票
public class Ticket implements Runnable { private int tickets=100; private Object lock = new Object(); public void run(){ while(true){ synchronized (lock) { if(tickets>0){ try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+tickets--+"張票"); }else{ break; } } } }
同步方法
//同步方法:在方法聲明上加上synchronized public synchronized void method(){ //可能會產生線程安全問題的代碼 }
同步方法中的鎖對象是 this
使用同步方法,對電影院賣票案例中Ticket類進行如下代碼修改:
package com.oracle.demo01; public class Ticket implements Runnable { private int tickets=100; private Object lock = new Object(); public void run() { //模擬賣票 while(true){ //同步方法 method(); } } //同步方法,鎖對象this public synchronized void method(){ if (tickets> 0) { //模擬選坐的操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+tickets--+"張票"); } } }
//靜態同步方法: 在方法聲明上加上static synchronized public static synchronized void method(){ //可能會產生線程安全問題的代碼 }
靜態同步方法中的鎖對象是本類字節碼對象 類名.class
同步鎖:對象鎖,對象監視器
同步是怎么保證安全性?
因為同步中有鎖,沒有鎖的線程,不能執行,只能等。
StringBuilder比StringBuffer快
原因:StringBuffer有同步關鍵字synchronized,安全性比StringBuilder高,但速度慢
死鎖
同步鎖使用的弊端:當線程任務中出現了多個同步(多個鎖)時,如果同步中嵌套了其他的同步。這時容易引發一種現象:程序出現無限等待,這種現象我們稱為死鎖。
格式如:
synchronzied(A鎖){ synchronized(B鎖){ } }
定義鎖對象類:
package com.oracle.demo01; public class LockA { public final static LockA locka=new LockA(); private LockA(){ } }
package com.oracle.demo01; public class LockB { public final static LockB lockb=new LockB(); private LockB(){ } }
線程任務類:
package com.oracle.demo01; public class DeadLock implements Runnable { private int i=0; public void run() { while(true){ if(i%2==0){ //情況一 synchronized (LockA.locka){ System.out.println("IF......lockA"); synchronized (LockB.lockb){ System.out.println("IF......lockB"); } } }else{ //情況二 synchronized (LockB.lockb){ System.out.println("else......lockB"); synchronized (LockA.locka){ System.out.println("else......lockA"); } } } i++; } } }
測試類:
package com.oracle.demo01; public class TestDEADlock { public static void main(String[] args) { //創建線程任務類對象 DeadLock dl=new DeadLock(); //創建兩個線程 Thread t1=new Thread(dl); Thread t2=new Thread(dl); //開啟線程 t1.start(); t2.start(); } }
Lock接口
使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,對電影院賣票案例中Ticket類進行如下代碼修改:
package com.oracle.demo01; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class NewTicket implements Runnable{ private int tickets=100; //創建Lock鎖對象 private Lock ck = new ReentrantLock(); public void run(){ while(true){ ck.lock(); if(tickets>0){ try { Thread.sleep(10); System.out.println(Thread.currentThread().getName()+"出售第"+tickets--+"張票"); } catch (InterruptedException e) { e.printStackTrace(); }finally{ ck.unlock();//釋放鎖 } } } } }