線程
多線程就是一個程序中有多個線程在同時執行。
多線程下CPU的工作原理
實際上,CPU(中央處理器)使用搶占式調度模式在多個線程間進行著高速的切換。對于CPU的一個核而言,某個時刻,只能執行一個線程,而CPU的在多個線程間切換速度相對我們的感覺要快,看上去就是在同一時刻運行。
其實,多線程程序并不能提高程序的運行速度,但能夠提高程序運行效率,讓CPU的使用率更高。
一、創建線程
方法1:繼承Thread類,重寫run方法
public class SubThread extends Thread{public SubThread(){super("x5456"); //通過構造方法修改線程名}public void run() {for(int i=0;i<100;i++){System.out.println(super.getName()+i);}}
}
?調用:
public static void main(String[] args) {//創建剛剛繼承Thread類的子類的對象SubThread st = new SubThread();//通過setName方法,修改線程名st.setName("x54256");//調用對象的start方法,會自動執行我們重寫的run方法st.start();for(int i=0;i<100;i++) {System.out.println(Thread.currentThread().getName()+i); //獲取當前線程的對象,調用getname()方法}
}
方法2:實現接口Runnable,重寫run方法
public class SubRunnable implements Runnable{public void run(){for(int i=0;i<100;i++){try {// 調用Thread類的sleep方法,休眠50ms,由于父接口沒有throws異常,so我們只能用try...catchThread.sleep(50); } catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"..."+i);}}
}
調用:
public static void main(String[] args) {//創建實現Runnable接口的類的對象SubRunnable sr = new SubRunnable();//創建Thread類的對象Thread t = new Thread(sr);//啟動線程t.start();for(int i=0;i<100;i++){System.out.println(Thread.currentThread().getName()+"..."+i);}
}
方法3:使用匿名內部類,實現多線程程序
匿名內部類的前提:繼承或者接口實現
使用方法:
new 父類或者接口(){
重寫抽象方法
}
public static void main(String[] args) {//繼承方式 XXX extends Thread{ public void run(){}}new Thread(){public void run(){System.out.println("!!!");}}.start();//實現接口方式 XXX implements Runnable{ public void run(){}}Runnable r = new Runnable(){public void run(){System.out.println("###");}};new Thread(r).start();//==================或=====================new Thread(new Runnable(){public void run(){System.out.println("@@@");}}).start();}?
實現接口的好處:
高內聚,低耦合:模塊內能做的事就自己做,模塊間的關系要盡量的小
第二種方式實現Runnable接口避免了單繼承的局限性,所以較為常用。實現Runnable接口的方式,更加的符合面向對象,線程分為兩部分,一部分線程對象,一部分線程任務。繼承Thread類,線程對象和線程任務耦合在一起。一旦創建Thread類的子類對象,既是線程對象,有又有線程任務。實現runnable接口,將線程任務單獨分離出來封裝成對象,類型就是Runnable接口類型。Runnable接口對線程對象和線程任務進行解耦。?
多線程的內存圖解:
線程的一生:?
二、線程池
線程池,其實就是一個容納多個線程的容器,其中的線程可以反復使用,省去了頻繁創建線程對象的操作,無需反復創建線程而消耗過多資源。
在java中,如果每個請求到達就創建一個新線程,開銷是相當大的。在實際使用中,創建和銷毀線程花費的時間和消耗的系統資源都相當大,甚至可能要比在處理實際的用戶請求的時間和資源要多的多。除了創建和銷毀線程的開銷之外,活動的線程也需要消耗系統資源。如果在一個jvm里創建太多的線程,可能會使系統由于過度消耗內存或“切換過度”而導致系統資源不足。為了防止資源不足,需要采取一些辦法來限制任何給定時刻處理的請求數目,盡可能減少創建和銷毀線程的次數,特別是一些資源耗費比較大的線程的創建和銷毀,盡量利用已有對象來進行服務。
線程池主要用來解決線程生命周期開銷問題和資源不足問題。通過對多個任務重復使用線程,線程創建的開銷就被分攤到了多個任務上了,而且由于在請求到達時線程已經存在,所以消除了線程創建所帶來的延遲。這樣,就可以立即為請求服務,使用應用程序響應更快。另外,通過適當的調整線程中的線程數目可以防止出現資源不足的情況。
方法1:使用線程池方式--Runnable接口
public static void main(String[] args) {//調用工廠類的靜態方法,創建線程池對象(ExecutorService接口的實現類)//返回線程池對象,是返回的接口ExecutorService es = Executors.newFixedThreadPool(2); //池內有2個線程//調用接口實現類對象es中的方法submit提交線程任務//將Runnable接口實現類對象,傳遞es.submit(new SubRunnable());es.submit(new SubRunnable());es.submit(new SubRunnable());es.submit(new SubRunnable());
}
實現的Runnable接口
public class ThreadPoolRunnable implements Runnable {public void run(){System.out.println(Thread.currentThread().getName()+" 線程提交任務");}
}
方法2:使用線程池方式—Callable接口
之前的實現方法,線程運行完沒有返回值,而且不能拋異常。
Callable接口:與Runnable接口功能相似,用來指定線程的任務。其中的call()方法,用來返回線程任務執行完畢后的結果,call方法可拋出異常。
public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService es = Executors.newFixedThreadPool(2);//提交線程任務的方法submit方法返回 Future接口的實現類Future<Integer> f = es.submit(new SubCallable());//獲取返回值Integer i = f.get();System.out.println(i);
}
實現的Callable接口
public class SubCallable implements Callable<Integer>{@Overridepublic Integer call() {return 123;}
}
使用線程實現異步計算
private int a;
//通過構造方法傳參
public GetSumCallable(int a){this.a=a;
}public Integer call(){int sum = 0 ;for(int i = 1 ; i <=a ; i++){sum = sum + i ;}return sum;
}
ThreadPoolDemo.java
/** 使用多線程技術,求和* 兩個線程,1個線程計算1+100,另一個線程計算1+200的和* 多線程的異步計算*/
public class ThreadPoolDemo {public static void main(String[] args)throws Exception {ExecutorService es = Executors.newFixedThreadPool(2);Future<Integer> f1 =es.submit(new GetSumCallable(100));Future<Integer> f2 =es.submit(new GetSumCallable(200));System.out.println(f1.get());System.out.println(f2.get());es.shutdown();}
}
FutureTask的使用
FutureTask繼承了Callable和Future接口,所以它既能像callable一樣被Thread執行,也能像Future那樣獲取結果。
@Overridepublic boolean testServiceUrl(ServiceUrlTestDTO url){// 調用地圖服務Callable<Boolean> callable = new WebServiceUtil(url.getUrl(),url.getProxy(),url.getToken());FutureTask<Boolean> futureTask = new FutureTask<>(callable);Thread thread = new Thread(futureTask);thread.start();try {Boolean isOK = futureTask.get();System.out.println("服務【"+ url.getUrl() +"】測試:"+ isOK);return isOK;} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}return false;}