Java基礎知識整理:多線程

概念

1:程序 是一個靜態的概念。 是一系列 計算機指令的集合。

2:進程:是一個動態的概念,程序的一次執行,會產生一個進程。或者進程可以看作是運行的程序。

進程特點:
1:進程可以併發(不是並行執行)執行。
並行執行:兩個程序在同一個時間一起被執行。

   2:進程是cpu+操作系統 進行資源分配的最小單元。
           當程序執行的時候,進程被創建,然後系統會對該進行分配只供它自己使用的資源,多個進程之間不共享資源。

3:線程:是進程中的一條完整的執行的路徑。可以看作是進程中的一條任務線。

   進程是運行的程序,進程是爲了完成某些任務的。
   1:進程可以看作是一個線程的容器,所有進程中的任務都是依賴於進程中的線程來完成的。
   2:一個進程中至少要包含一個線程。
   3:一個進程中可以包含多個線程,那麼這多個線程就共享了所在的進程的資源。
   4:一個進程中的第一個線程,稱爲是主線程。java 中的主線程 是 main 線程。
   5:線程可以併發執行。一個進程中可以同時運行多個線程。

舉個栗子:
把進程看作高速公路,把線程看作高速公路上的車道。
把一個車道看作一個線程。

迅雷是一個進程。
迅雷中有5條下載的任務。可以看作是5個線程。

進程和線程的關係:

   1:線程是不能獨立存在的,必須在進程中存活。
   2:進程的所有的任務都依賴於進程中的線程來完成。
   3:進程是線程的載體,線程負責完成進程的任務。
   4:一個進程可以有任意個線程,至少一個線程。第一個線程稱爲主線程。 java 中的第一個線程爲main線程。        
   5:進程是cpu進行資源分配的最小單位。
   6:線程是cpu進行調度執行的最小單位。
   7:可以將線程看作輕量級的進程。系統創建進程,銷燬進程 都會消耗比較大的資源。但是創建線程,銷燬線程消耗資源比較小。
   8:一個進程中的所有的線程共享所在進程的資源。

單線程程序的問題:

   1:如果程序中存在阻塞式方法。。nextInt,線程就被阻塞了。程序就不能繼續執行了。
   2:多線程程序中,如果一個線程被阻塞了,其他的線程不受任何的影響。

多線程的例子:

   qq 的登錄窗口,
   一個線程用於監聽服務器的反饋信息。一旦服務器有返回的信息,那麼必須立即響應。
   一個線程用於窗口動畫的繪製。這樣監聽程序被阻塞了,不會影響動畫的繪製。

線程的實現

在java 中提供了類 java.lang.Thread 用來描述線程對象。

java 中被執行的代碼,都是被某一個線程執行的。

A; 是一條代碼語句,那麼當前線程的意思,就是執行 A 語句的線程對象。

java 中創建線程的方式:
3種方式。
1:繼承 java.lang.Thread 類。
2:實現java.lang.Runnable 接口。
3: 實現 java.util.concurrent.Callable.(jdk 1.5之後出現的)

/**
* 使用繼承線程類的方式,實現創建線程
* @author yhl
*
*/
public class MyThreadTest {
       //main 主線程 執行的主體代碼就是main 方法的方法體。
       public static void main(String[] args) {
               
               //先創建一個自定義線程對象
               MyThread thread = new MyThread();
               //啓動線程對象,開始執行run。通過調用線程對象的start 方法啓動線程,千萬不要調用run 方法。
               thread.start();
               
               
               //主線程 完成的任務  也是 不斷累加
               int counter = 0;
               while(true){
                       System.out.println(Thread.currentThread().getName()+"-->"+counter ++);
               }
               
       }

}

//自定義線程類    線程完成的任務是 讓一個計數器不斷的累加輸出。
class MyThread extends Thread{
       //就是該線程的任務的主體部分
       public void run() {
               int counter = 0;
               while(true){
                       System.out.println(Thread.currentThread().getName()+"-->"+counter ++);
               }
       }
       
}

1:繼承線程類 java.lang.Thread:
優點:容易理解、代碼實現也比較簡單。
缺點:多個線程對象共享同一個數據,相對複雜、已經繼承了Thread ,不能再繼承其他的類。

2:實現接口 java.lang.Runnable
優點:多個線程共享同一個數據,相對簡單。可以再繼承其他的類。
缺點:不容理解,使用了代理模式,代碼實現相對複雜。

線程的生命週期

priority

線程的優先級:

Thread 類中兩個方法:

得到線程的優先級:int getPriority()

設置線程的優先級:void setPriority(int priority)

線程優先級的大小:
/**
* The minimum priority that a thread can have.
/
public final static int MIN_PRIORITY = 1;
/
*
* The default priority that is assigned to a thread.
/
public final static int NORM_PRIORITY = 5;
/
*
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;

如果設置線程的優先級 不在[1-10]之間,那麼會拋出一個非法參數異常。

當一個線程創建之後,默認的優先級是 5. 主線程 main 的優先級就是5.

優先級比較小的線程,被cpu 調度執行的概率要低一些。

jvm 中的 垃圾回收器 工作由單獨的一個線程來處理。該線程的優先級是所有的工作線程中最低的。
所以垃圾回收器工作的時機不確定。

public class ThreadPriorityTest {

       public static void main(String[] args) {
               
               PriorityThread thread = new PriorityThread();
               //先設置優先級後啓動線程
               thread.setPriority(Thread.MAX_PRIORITY);
               thread.start();
               
               int counter = 0 ;
               while(true){
                       System.out.println(Thread.currentThread().getName() + "-->"+counter ++);
               }
       }

}

class PriorityThread extends Thread{
       @Override
       public void run() {
               int counter = 0 ;
               while(true){
                       System.out.println(Thread.currentThread().getPriority() + "-->"+counter ++);
               }
       }
}

join

void join()
等待該線程終止。
void join(long millis)
等待該線程終止的時間最長爲 millis 毫秒。 多了一個解除阻塞原因的條件。(線程終止| 時間到了)
void join(long millis, int nanos)
等待該線程終止的時間最長爲 millis 毫秒 + nanos 納秒。

public class JoinTest {

       public static void main(String[] args) throws Exception{
               //新建狀態
               ChengThread thread = new ChengThread();
               thread.setName("程咬金");
               
               for(int i=0;i<10;i++){
                       if(i == 5){
                               //就緒狀態  等待cpu 調度
                               thread.start();
                               //導致當前線程(誰執行這句代碼誰就是當前線程)進入阻塞狀態
                               //立即執行 插隊的線程(從就緒狀態進入運行狀態),
                               //當 插隊線程執行完畢之後,阻塞原因解除,被阻塞的線程從
                               //阻塞狀態進入就緒狀態,等待cpu 的下次調度。
                               //先start 後 join。
                               thread.join();
                       }
                       System.out.println(Thread.currentThread().getName() + "-->"+ i);
               }
       }

}

class ChengThread extends Thread{
       @Override
       public void run() {
               for(int i=0;i<10;i++){
                       System.out.println(Thread.currentThread().getName() + "-->"+ i);
               }
       }
       
}

sleep

Thread.sleep(mills);
讓當前線程進入阻塞狀態(休眠),阻塞狀態的時間爲指定的時間(毫秒)。從進入休眠時開始倒計時,經歷指定的休眠時間之後,
該線程自動從阻塞狀態,進入就緒狀態。等待cpu 下次調度。

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SleepTest {

       public static void main(String[] args) throws Exception {
               test2();
       }
       
       //模擬倒計時
       static void test1() throws Exception{
               System.out.println("READY!");
               for(int i=3;i>0;i--){
                       System.out.println(i);
                       Thread.sleep(1000);
               }
               System.out.println("GO!");
       }
       
       
       //計時器 每隔一秒輸出一次當前時間 yyyy-MM-dd HH:mm:ss
       //線程執行週期的時間精確控制
       static void test2() throws Exception{
               Date date = new Date();
               DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
               while(true){
                       long time = System.currentTimeMillis();
                       String str = sdf.format(date);
                       System.out.println(str);
                       date.setSeconds(date.getSeconds()+1);
                       long cost = System.currentTimeMillis()-time;
                       Thread.sleep(1000-cost);
               }
       }
       
       

}

yield

yield():
Thread 類的一個靜態方法。
作用:讓當前線程從運行狀態進入就緒狀態,等待cpu 的下次的調度。立即出讓對cpu 的控制使用權。

public class YieldTest {

       public static void main(String[] args) {
               YieldThread thread1 = new YieldThread();
               YieldThread thread2 = new YieldThread();
               
               thread1.start();
               thread2.start();
       }

}

//理論的理想的狀態應該是 2個線程交替打印
class YieldThread extends Thread{
       
       public void run() {
               for(int i=0;i<10;i++){
                       System.out.println(Thread.currentThread().getName() + "-->"+i);
                       //立即釋放cpu 的控制權,進入就緒狀態。
                       Thread.yield();
               }
       }
}


daemon

線程被分爲兩類:
1:用戶線程 User Thread
2:守護線程 Daemon Thread

守護線程的特點:
如果一個進程中存活的線程,只有守護線程了(不管有多少個守護線程),那麼jvm退出。
進程的存活與否只與用戶線程有關,只要一個進程中包含了一個存活的用戶線程,那麼該進程就不會被jvm 殺死。就是一個存活的進程。
線程創建之後,默認爲用戶線程,通過調用 thread.setDaemon(true);來設置 thread 對象爲守護線程。

public final void setDaemon(boolean on)
將該線程標記爲守護線程或用戶線程。當正在運行的線程都是守護線程時,Java 虛擬機退出。
該方法必須在啓動線程前調用。

通過isDaemon() 來判斷 線程對象是否爲守護線程。

main 線程不能作爲守護線程。

守護線程的作用:
一般用來給用戶線程提供服務的。
比如我們的 GC 線程。守護線程。用來給用戶線程擦屁股的。如果用戶線程都掛了。所以 GC 生無可戀,也就去了。

public class DaemonTest {

       public static void main(String[] args) throws Exception{
               DaemonThread thread = new DaemonThread();
               //設置指定的線程爲守護線程。
               thread.setDaemon(true);
               thread.start();
               
               for(int i=0;i<10;i++){
                       System.out.println(Thread.currentThread().getName() + "-->" + i);
                       Thread.sleep(300);
               }
       }

}

class DaemonThread extends Thread{
       @Override
       public void run() {
               while(true){
                       System.out.println("DaemonThread.run()");
                       try {
                               Thread.sleep(300);
                       } catch (InterruptedException e) {
                               e.printStackTrace();
                       }
               }
       }
}

每一個被創建的線程,都會被分配一個唯一的不同於其他線程的ID。

public long getId()
返回該線程的標識符。線程 ID 是一個正的 long 數,在創建該線程時生成。線程 ID 是唯一的,並終生不變。線程終止時,該線程 ID 可以被重新使用。

可以通過線程的ID 來唯一的 區分 線程對象。

線程安全

線程安全的問題,是針對多線程的程序。單線程的情況下,是不存在線程安全問題。

產生線程安全問題的原因:
多個線程同時訪問同一塊代碼。但是實際上我們希望該代碼塊是原子性的,在某一個時間點,只希望一個線程單獨訪問,不希望多個線程同時訪問。

解決方案:

   1:同步代碼塊。
   synchronized (this) {
           //被同步的代碼塊;
   }

   synchronized(同步監視器對象) :java 關鍵字
   {}:同步代碼塊:希望在某一個時間點只有一個線程訪問的代碼塊。


   執行過程:
           1:線程1 執行到了同步代碼塊。要對同步監視器進行檢測,看是否被其他的線程上鎖了。發現沒有上鎖,那麼線程1
                   對同步監視器對象 上鎖,並開始訪問 同步代碼塊。
           2:線程2 執行到了同步代碼塊,檢測同步監視器,發現監視器已經被 線程1 上鎖了。就進入就緒狀態,等待cpu 下次調度執行。
           3:線程1 執行完畢同步代碼塊,然後對 同步監視器對象  解鎖。並執行後續的代碼。
           4:當 線程2 被再次調度執行的時候,發現同步監視器對象已經被解鎖,那麼就對同步監視器對象 加鎖 並訪問 同步代碼塊。


   類似於 上廁所。
   沒人,進去,鎖門,便便,然後開門出去。
   next one。


   2:同步方法:
           相當於把整個方法體都同步了。
           同步監視器對象 是  this(實例方法)。
           同步方法,會導致在任意時刻,只能有一個線程訪問該方法。一個線程執行完畢方法之後,其他的線程才能訪問。

同步塊和同步方法都會導致程序的效率降低。多了檢測監視器對象是否加鎖,加鎖和解鎖的過程。

   3:如果同步方法是靜態方法,那麼同步監視器對象是當前類的大 Class 對象。

Lock-線程安全

關於監視器的選擇

如果想實現線程間的互斥訪問 同步代碼塊,那麼監視器對象必須 唯一。只有一個實例存在。

1:this:保證只有一個this。這個對象只被實例化了一次。
2:final static Object o = new Object();

3: 當一個類加載的時候,類加載只有一次,會產生2部分內容。1部分是類的字節碼元數據(方法區)。還會生成一個對象(堆區)。對象的類型是 描述類的類型 Class。
這個大Class 類型的對象也是唯一的。可以通過 類名.class 來得到這個唯一的描述當前類的對象。

   好處:不可變 以及  唯一性。
   當前類的 大Class 對象。

線程死鎖:

static Object o1 = new Object();

static Object o2 = new Object();

//線程-1 執行的代碼
synchronized(o1){
// thread-1 stop here
synchronized(o2){

   }

}

//線程-2 執行 代碼
synchronized(o2){
// thread-2 stop here
synchronized(o1){

   }

}

出現死鎖的條件:
1:同步代碼塊嵌套。
2:多個線程之間已經鎖住的資源爲彼此請求鎖住的資源。

如果死鎖已經產生,那麼沒有辦法解決,只能在編碼的時候避免出現死鎖。
避免出現嵌套的同步代碼塊。

public class DeadLockTest {

       public static void main(String[] args) {
               new DeadLockThread(0).start();
               new DeadLockThread(1).start();
       }

}

class DeadLockThread extends Thread{
       //兩個監視器對象
       final static Object O1 = new Object();
       final static Object O2 = new Object();
       
       private int id;
       
       public DeadLockThread(int id) {
               this.id = id;
       }
       
       @Override
       public void run() {
               if(id == 0){
                       synchronized (O1) {
                               System.out.println(Thread.currentThread().getName()+"--O1--->O2");
                               try {
                                       Thread.sleep(100);
                               } catch (InterruptedException e) {
                                       e.printStackTrace();
                               }
                               synchronized (O2) {
                                       System.out.println(Thread.currentThread().getName()+"--O1--->O2--------");
                               }
                       }
               }else{
                       synchronized (O2) {
                               System.out.println(Thread.currentThread().getName()+"--O2--->O1");
                               synchronized (O1) {
                                       System.out.println(Thread.currentThread().getName()+"--O2--->O1--------");
                               }
                       }
               }
       }
       
}

線程間通信-管道流

使用管道流實現多個線程之間的信息的交互。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

/**
* 兩個線程 獨立的 類,進行信息的交互
* 管道輸出流寫出的數據,被管道輸入流讀取到。
* 在一個線程中用管道輸出流寫出數據。
* 在另外一個線程中使用管道輸入流讀取 管道輸出流寫出的數據
* @author yhl
*
*/
public class PipedStreamTest {

       public static void main(String[] args) throws Exception {
               PipedInputStream pis = new PipedInputStream();
               PipedOutputStream pos = new PipedOutputStream();
               //可以通過構造方法將管道輸入輸出流建立關聯,也可以通過方法。
               pis.connect(pos);
               
               PipedOutputStreamThread thread1 = new PipedOutputStreamThread(pos);
               PipedInputStreamThread thread2 = new PipedInputStreamThread(pis);
               
               new Thread(){
                       public void run() {
                               thread2.start();
                       };
               }.start();
               
               Thread.sleep(5000);
               
               new Thread(){
                       public void run() {
                               thread1.start();
                       };
               }.start();
       }

}

//管道輸出流的線程類
class PipedOutputStreamThread extends Thread{
       //持有管道輸出流的引用。
       private PipedOutputStream pos;
       
       public PipedOutputStreamThread(PipedOutputStream pos) {
               this.pos = pos;
       }
       
       public void run() {
               try {
                       //使用管道輸出流寫出數據
                       String str = "多年不見,你還好麼?";
//                        byte[] bs = str.getBytes();
                       BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(pos));
                       bw.write(str);
//                        pos.write(bs);
                       bw.newLine();
                       bw.flush();
                       bw.close();
               } catch (Exception e) {
                       e.printStackTrace();
               }finally {
                       try {
                               pos.close();
                       } catch (IOException e) {
                               e.printStackTrace();
                       }
               }
       }
}

//用於讀取管道輸出流寫出數據的輸入流
class PipedInputStreamThread extends Thread{
       private PipedInputStream pis;
       public PipedInputStreamThread(PipedInputStream pis) {
               this.pis = pis;
       }
       @Override
       public void run() {
               try {
                       BufferedReader br = new BufferedReader(new InputStreamReader(pis));
                       String str = br.readLine();
                       System.out.println("接收到的內容爲:"+str);
                       br.close();
               } catch (Exception e) {
                       e.printStackTrace();
               }finally{
                       try {
                               pis.close();
                       } catch (IOException e) {
                               e.printStackTrace();
                       }
               }
       }
}

生產者消費者代碼

主要認識三個方法:

都是Object類的方法。
Object o = new Object();

o.wait():讓當前線程在當前對象上等待。 讓執行這句代碼的線程在o 上等待。
o.notify(): 將在o 對象上等待的某個線程 喚醒。
o.notifyAll(): 將在o 對象上等待的所有的線程喚醒。

這三個方法使用的環境:
必須讓當前線程持有當前對象監視器的鎖。
o.wait() 表示 當前線程必須對 o 上鎖了。

問題1:
1:消費者1 開始消費,等待了。
2:消費者2 開始消費,等待了。
3:生產者 生產了一個饅頭,喚醒了 消費者1. 生產者繼續生產,等待了。
4:消費者1 開始消費,結果把消費者2 喚醒了。消費者1 等待了。
5:消費者2 開始消費,容器已經是空的了。 數組下標越界。

單例模式最終版

public class SingleTonTest {
       
       static int num = 0;

       public static void main(String[] args) {
               
               //在線程1 中 得到唯一的實例。
               new Thread(){
                       @Override
                       public void run() {
                               MySingleton singleton1 = MySingleton.getInstance();
                               System.out.println(singleton1);
                       }
               }.start();
               
               
               //在線程2中再得到一次
               new Thread(){
                       @Override
                       public void run() {
                               MySingleton singleton2 = MySingleton.getInstance();
                               System.out.println(singleton2);
                       }
               }.start();
               
               //使用Runnable 接口,匿名內部類
               new Thread(new Runnable() {
                       @Override
                       public void run() {
                               
                       }
               }).start();
               
               //面試題
               new Thread(new Runnable() {// 11111
                       @Override
                       public void run() {
                               System.out.println(22222);
                       }
               }){
                       public void run() {
                               System.out.println(11111);
                       };
               }.start();
               
       }

}

//懶漢模式單例類
class MySingleton{
       
       private int num = 10;
       
       private static MySingleton singleton;
       
       private MySingleton(){}
       
       //對外的提供的唯一的 方位 唯一實例的方法
       public /*synchronized*/ static MySingleton getInstance(){
               //爲了提高效率,後續的該方法的訪問都不會再進行 加鎖和解鎖的 和監視器是否有鎖的判斷
               if(singleton == null){
                       synchronized (MySingleton.class) {
                               //爲了保證唯一的實例
                               if(singleton == null){
                                       try {
                                               Thread.sleep(1000);
                                       } catch (InterruptedException e) {
                                               e.printStackTrace();
                                       }
                                       singleton = new MySingleton();
                               }
                       }
               }
               return singleton;
       }
       
       public void setNum(int num){
               this.num = num;
       }
       
       public int getNum(){
               return num;
       }
       
}

wait-notify

Object obj = new Object();
obj.wait(): 讓當前線程在 obj上 等待。在調用obj.wait 之前,當前線程必須 對 obj上鎖。調用obj.wait()之後,當前線程立即 釋放 對obj 的鎖,並等待進入阻塞狀態。
當其他的線程 調用 obj.notify() 或者調用 obj.notifyAll() 的時候。進入阻塞狀態的 線程 [被喚醒]。從阻塞狀態進入 就緒狀態。等待cpu 的調度執行。該線程再次執 行的時候,需要先對obj 繼續上鎖,然後從 obj.wait() 後繼續執行。

obj.notify(): 喚醒 在 obj 上等待(阻塞,調用了obj.wait()方法)的 某一個線程。讓某一個線程從 等待隊列中,進入請求資源隊列中,進入就緒狀態。

obj.notifyAll(): 喚醒所有在 obj 上等待的線程。從阻塞狀態進入就緒狀態。

加時間參數的wait(time) 阻塞的線程最多等待的時間爲 time。 解除阻塞的條件多了一個。一個是 時間到了,一個是被notify 喚醒了。

sleep 和 wait 區別:
1:sleep 沒有要求必須在同步代碼塊|同步方法中使用。 wait()必須在同步塊|同步方法中使用。
2:sleep 如果在同步代碼塊中使用,那麼休眠的過程中,不會釋放對監視器的鎖。wait() 會釋放對監視器的鎖。
3:解除阻塞的條件不同:sleep 休眠時間到了,自動從阻塞狀態進入就緒狀態。 wait() 必須被其他的線程喚醒,從阻塞到就緒狀態。

jdk1.5 線程

Callable<泛型>
FutureTask<泛型>
Future<>


package com.bjsxt.thread;

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
* jdk1.5  線程
* @author yhl
*
*/
public class CallableTest {

       public static void main(String[] args) throws Exception{
               RandomCallable callable = new RandomCallable();
               //FutureTask 是Runnable 和 Future 唯一的實現的子類。
               FutureTask<Integer> task = new FutureTask<>(callable);
               //Future 接口,主要用來負責 線程任務的調度和判斷的執行。
               //先創建一個線程對象
               Thread thread = new Thread(task);
               thread.start();
               
               System.out.println(task.isDone());
               //get 是一個阻塞式的方法,直到等到線程call 方法返回,得到結果。get 才返回。
               System.out.println(task.get());
               System.out.println(task.isDone());
               
       }
}


//得到一個隨機的正整數[0~10]
//泛型中的類型爲 線程執行得到的結果的類型
class RandomCallable implements Callable<Integer>{

       //和之前的run 方法是類型的。是線程的任務的主體代碼。
       //和 run 方法的區別
       //1: call 方法可以指定 返回的類型。線程執行完畢之後會有一個結果。
       //2: call 方法 自定義了拋出異常,在方法體中,如果有異常拋出,不需要再必須顯式的 try-catch 。
       public Integer call() throws Exception {
               Thread.sleep(5000);
               return new Random().nextInt(11);//[0~11)
       }
       
}



中斷線程

public void interrupt():設置線程的中斷標識位爲true。並不會真的中斷線程。如果遇到阻塞的狀態,那麼會拋出一個InterruptedException,並清除中斷標識位。
public static boolean interrupted():判斷線程的中斷標識位的狀態,並清除該狀態爲false。
public boolean isInterrupted():判斷線程的中斷標識位的狀態,不會影響狀態。

中斷應用

1:使用中斷信號量中斷非阻塞狀態的線程

中斷線程最好的,最受推薦的方式是,使用共享變量(shared variable)發出信號,告訴線程必須停止正在運行的任務。線程必須週期性的核查這一變量,然後有秩序地中止任務。Example2描述了這一方式:

public void interrupt():設置線程的中斷標識位爲true。並不會真的中斷線程。如果遇到阻塞的狀態,那麼會拋出一個InterruptedException,並清除中斷標識位。
public static boolean interrupted():判斷線程的中斷標識位的狀態,並清除該狀態爲false。
public boolean isInterrupted():判斷線程的中斷標識位的狀態,不會影響狀態。

中斷應用

1:使用中斷信號量中斷非阻塞狀態的線程

中斷線程最好的,最受推薦的方式是,使用共享變量(shared variable)發出信號,告訴線程必須停止正在運行的任務。線程必須週期性的核查這一變量,然後有秩序地中止任務。Example2描述了這一方式:

/**
* 線程中斷
* @author yhl
*
*/
public class InterruptThreadTest {

       public static void main(String[] args) throws InterruptedException {
               
//                InterruptThread thread = new InterruptThread();
//                thread.start();
//                
//                Thread.sleep(1000);
//                //3秒之後中斷線程
//                thread.setFlag(false);
               
               InterruptThread1 thread = new InterruptThread1();
               thread.start();
               
               Thread.sleep(1000);
               //中斷線程   設置線程中斷標識位爲 true
               thread.interrupt();
               
       }

}

//最常見的線程中斷的方式
class InterruptThread extends Thread{
       //通過修改 flag 標識位 ,來控制循環的時間
       //如果當前線程被阻塞了,是不能立即中斷的,必須等阻塞解除了才能中斷
       private boolean flag = true;
       @Override
       public void run() {
               int i = 0;
               while(flag){
//                        try {
//                                Thread.sleep(1000);
//                        } catch (InterruptedException e) {
//                                e.printStackTrace();
//                        }
                       //控制一秒的循環的週期,避免sleep 可能帶來的異常發生
                       long time = System.currentTimeMillis();
                       while(true){
                               if(System.currentTimeMillis()-time >=1000)
                                       break;
                       }
                       
                       System.out.println("你好 = "+i++);
               }
       }
       
       public void setFlag(boolean flag){
               this.flag =flag;
       }
}

//public void interrupt():
//設置線程的中斷標識位爲true。並不會真的中斷線程。如果遇到阻塞的狀態,那麼會拋出一個InterruptedException,並清除中斷標識位。
//public static boolean interrupted():
//判斷線程的中斷標識位的狀態,並清除該狀態爲false。
//public boolean isInterrupted():
//判斷線程的中斷標識位的狀態,不會影響狀態。
//通過修改線程的中斷標誌位 來控制線程的中斷
class InterruptThread1 extends Thread{
       @Override
       public void run() {
               int i = 0;
               //當線程的中斷標識位爲 false 的時候,執行線程
               while(false == isInterrupted()){
                       System.out.println("你好 = "+i++);
                       try {
                               Thread.sleep(3000);
                       } catch (InterruptedException e) {
                               e.printStackTrace();
                               //當線程在休眠的過程中,調用了interrupt() 方法,那麼線程會拋出一個異常
                               //InterruptedException 被捕獲,中斷標誌位 被還原爲 false。
                               //如果想在此刻中斷線程,那麼需要重新調用 interrupt() 方法,將中斷標誌位 設置爲 true。
                               //纔可以中斷線程。
                               interrupt();
                       }
               }
       }
}


再說線程同步

同步塊-同步方法

進行多線程編程,同步控制是非常重要的,而同步控制就涉及到了鎖。
對代碼進行同步控制我們可以選擇同步方法,也可以選擇同步塊,這兩種方式各有優缺點,至於具體選擇什麼方式,就見仁見智了,同步塊不僅可以更加精確的控制對象鎖,也就是控制鎖的作用域,何謂鎖的作用域?鎖的作用域就是從鎖被獲取到其被釋放的時間。而且可以選擇要獲取哪個對象的對象鎖。但是如果在使用同步塊機制時,如果使用過多的鎖也會容易引起死鎖問題,同時獲取和釋放所也有代價,而同步方法,它們所擁有的鎖就是該方法所屬的類的對象鎖,換句話說,也就是this對象,而且鎖的作用域也是整個方法,這可能導致其鎖的作用域可能太大,也有可能引起死鎖,同時因爲可能包含了不需要進行同步的代碼塊在內,也會降低程序的運行效率。而不管是同步方法還是同步塊,我們都不應該在他們的代碼塊內包含無限循環,如果代碼內部要是有了無限循環,那麼這個同步方法或者同步塊在獲取鎖以後因爲代碼會一直不停的循環着運行下去,也就沒有機會釋放它所獲取的鎖,而其它等待這把鎖的線程就永遠無法獲取這把鎖,這就造成了一種死鎖現象。

靜態同步方法-非靜態同步方法

所有的非靜態同步方法用的都是同一把鎖–實例對象本身,也就是說如果一個實例對象的非靜態同步方法獲取鎖後,該實例對象的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖後才能獲取鎖,可是別的實例對象的非靜態同步方法因爲跟該實例對象的非靜態同步方法用的是不同的鎖,所以毋須等待該實例對象已獲取鎖的非靜態同步方法釋放鎖就可以獲取他們自己的鎖。
而所有的靜態同步方法用的也是同一把鎖–類對象本身,這兩把鎖是兩個不同的對象,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的。但是一旦一個靜態同步方法獲取鎖後,其他的靜態同步方法都必須等待該方法釋放鎖後才能獲取鎖,而不管是同一個實例對象的靜態同步方法之間,還是不同的實例對象的靜態同步方法之間,只要它們同一個類的實例對象!

同步鎖的選擇

而對於同步塊,由於其鎖是可以選擇的,所以只有使用同一把鎖的同步塊之間纔有着競態條件,這就得具體情況具體分析了,但這裏有個需要注意的地方,同步塊的鎖是可以選擇的,但是不是可以任意選擇的!!!!這裏必須要注意一個物理對象和一個引用對象的實例變量之間的區別!使用一個引用對象的實例變量作爲鎖並不是一個好的選擇,因爲同步塊在執行過程中可能會改變它的值,其中就包括將其設置爲null,而對一個null對象加鎖會產生異常,並且對不同的對象加鎖也違背了同步的初衷!這看起來是很清楚的,但是一個經常發生的錯誤就是選用了錯誤的鎖對象,因此必須注意:同步是基於實際對象而不是對象引用的!多個變量可以引用同一個對象,變量也可以改變其值從而指向其他的對象,因此,當選擇一個對象鎖時,我們要根據實際對象而不是其引用來考慮!作爲一個原則,不要選擇一個可能會在鎖的作用域中改變值的實例變量作爲鎖對象!!!!

Object

Object類的方法

1:toString(): 得到對象的字符串表示形式。
默認實現: 類的包名+類名+@+hashCode()
很多時候,在需要將一個對象的字符串表示形式輸出的時候,底層都調用了該方法。

2:equals(Object o): 用於比較兩個對象是否相等的。
默認實現:比較當前對象和 o 是否是同一個對象。

3:finalize(): 當對象所佔的內存被 GC 回收的時候,該對象的類型中的 finalize() 方法會被jvm 調用。
默認實現:空

4:hashCode() : 得到 對象的哈希碼 ,如果對象存在於HashSet 中,或者HashMap 的key ,那麼該對象的類型中需要重寫 hashCode 和 equals 方法。
默認實現:native 方法。 對象的內存地址轉換的一個整數值。不同的對象的默認的hashCode 的返回值是不同的。

5:wait(): 讓當前線程在 當前對象上等待,並釋放對當前對象的鎖。

6:notify(): 喚醒在當前對象上等待的某一個線程進入就緒狀態。

7:notifyAll() : 喚醒在當前對象上等待的所有的線程進入就緒狀態。

8:clone(): 複製對象的。只有實現了Cloneable 接口的類的對象 可以通過clone() 方法實現對象的複製。

9: getClass(): 得到當前對象的類型的大Class 對象。 類名.class 是類似的功能。

創建對象的方式:
1:通過new 關鍵字 調用構造方法去創建對象。
2:clone() 克隆
3:類的靜態工廠方法。
4:反序列化。
5:反射

線程池

1:Java中的ThreadPoolExecutor類

         java.uitl.concurrent.ThreadPoolExecutor類是線程池中最核心的一個類,因此如果要透徹地瞭解Java中的線程池,必須先了解這個類。下面我們來看一下ThreadPoolExecutor類的具體實現源碼。

          在ThreadPoolExecutor類中提供了四個構造方法:

public class ThreadPoolExecutor extends AbstractExecutorService {
   .....
   public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
           BlockingQueue<Runnable> workQueue);
   public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
           BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
   public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
           BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
   public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
       BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
   ...
}

corePoolSize:核心池的大小,這個參數跟後面講述的線程池的實現原理有非常大的關係。在創建了線程池後,默認情況下,線程池中並沒有任何線程,而是等待有任務到來才創建線程去執行任務,除非調用了prestartAllCoreThreads()或者prestartCoreThread()方法,從這2個方法的名字就可以看出,是預創建線程的意思,即在沒有任務到來之前就創建corePoolSize個線程或者一個線程。默認情況下,在創建了線程池後,線程池中的線程數爲0,當有任務來之後,就會創建一個線程去執行任務,當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中。

maximumPoolSize:線程池最大線程數,這個參數也是一個非常重要的參數,它表示在線程池中最多能創建多少個線程;

keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。默認情況下,只有當線程池中的線程數大於corePoolSize時,keepAliveTime纔會起作用,直到線程池中的線程數不大於corePoolSize,即當線程池中的線程數大於corePoolSize時,如果一個線程空閒的時間達到keepAliveTime,則會終止,直到線程池中的線程數不超過corePoolSize。但是如果調用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數不大於corePoolSize時,keepAliveTime參數也會起作用,直到線程池中的線程數爲0;

unit:參數keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態屬性:
TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小時
TimeUnit.MINUTES;           //分鐘
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //納秒


workQueue:一個阻塞隊列,用來存儲等待執行的任務,這個參數的選擇也很重要,會對線程池的運行過程產生重大影響,一般來說,這裏的阻塞隊列有以下幾種選擇:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;

ArrayBlockingQueue和PriorityBlockingQueue使用較少,一般使用LinkedBlockingQueue和Synchronous。線程池的排隊策略與BlockingQueue有關。

threadFactory:線程工廠,主要用來創建線程;

handler:表示當拒絕處理任務時的策略,有以下四種取值:
ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

Executor是一個頂層接口,在它裏面只聲明瞭一個方法execute(Runnable),返回值爲void,參數爲Runnable類型,從字面意思可以理解,就是用來執行傳進去的任務的;
然後ExecutorService接口繼承了Executor接口,並聲明瞭一些方法:submit、invokeAll、invokeAny以及shutDown等;
抽象類AbstractExecutorService實現了ExecutorService接口,基本實現了ExecutorService中聲明的所有方法;
然後ThreadPoolExecutor繼承了類AbstractExecutorService。
在ThreadPoolExecutor類中有幾個非常重要的方法:
       
execute():execute()方法實際上是Executor中聲明的方法,在ThreadPoolExecutor進行了具體的實現,這個方法是ThreadPoolExecutor的核心方法,通過這個方法可以向線程池提交一個任務,交由線程池去執行。
submit():submit()方法是在ExecutorService中聲明的方法,在AbstractExecutorService就已經有了具體的實現,在ThreadPoolExecutor中並沒有對其進行重寫,這個方法也是用來向線程池提交任務的,但是它和execute()方法不同,它能夠返回任務執行的結果,去看submit()方法的實現,會發現它實際上還是調用的execute()方法,只不過它利用了Future來獲取任務執行結果。
shutdown():按過去執行已提交任務的順序發起一個有序的關閉,但是不接受新任務。如果已經關閉,則調用沒有其他作用。
shutdownNow():嘗試停止所有的活動執行任務、暫停等待任務的處理,並返回等待執行的任務列表。在從此方法返回的任務隊列中排空(移除)這些任務。
並不保證能夠停止正在處理的活動執行任務,但是會盡力嘗試。 此實現通過 Thread.interrupt() 取消任務,所以無法響應中斷的任何任務可能永遠無法終止。

--------------------------------------------------------------------------------------------------------------------------------------

1.線程池狀態

在ThreadPoolExecutor中定義了一個volatile變量,另外定義了幾個static final變量表示線程池的各個狀態:
volatile int runState;
static final int RUNNING    = 0;
static final int SHUTDOWN   = 1;
static final int STOP       = 2;
static final int TERMINATED = 3;
runState表示當前線程池的狀態,它是一個volatile變量用來保證線程之間的可見性;
下面的幾個static final變量表示runState可能的幾個取值。
當創建線程池後,初始時,線程池處於RUNNING狀態;
如果調用了shutdown()方法,則線程池處於SHUTDOWN狀態,此時線程池不能夠接受新的任務,它會等待所有任務執行完畢;
如果調用了shutdownNow()方法,則線程池處於STOP狀態,此時線程池不能接受新的任務,並且會去嘗試終止正在執行的任務;
當線程池處於SHUTDOWN或STOP狀態,並且所有工作線程已經銷燬,任務緩存隊列已經清空或執行結束後,線程池被設置爲TERMINATED狀態。

------------------------------------------------------------------------------------------------------------------------------------
2.任務的執行

在瞭解將任務提交給線程池到任務執行完畢整個過程之前,我們先來看一下ThreadPoolExecutor類中其他的一些比較重要成員變量:

private final BlockingQueue<Runnable> workQueue;              //任務緩存隊列,用來存放等待執行的任務
private final ReentrantLock mainLock = new ReentrantLock();   //線程池的主要狀態鎖,對線程池狀態(比如線程池大小
                                                             //、runState等)的改變都要使用這個鎖
private final HashSet<Worker> workers = new HashSet<Worker>();  //用來存放工作集
private volatile long  keepAliveTime;    //線程存活時間  
private volatile boolean allowCoreThreadTimeOut;   //是否允許爲核心線程設置存活時間
private volatile int   corePoolSize;     //核心池的大小(即線程池中的線程數目大於這個參數時,提交的任務會被放進任務緩存隊列)
private volatile int   maximumPoolSize;   //線程池最大能容忍的線程數
private volatile int   poolSize;       //線程池中當前的線程數
private volatile RejectedExecutionHandler handler; //任務拒絕策略
private volatile ThreadFactory threadFactory;   //線程工廠,用來創建線程
private int largestPoolSize;   //用來記錄線程池中曾經出現過的最大線程數
private long completedTaskCount;   //用來記錄已經執行完畢的任務個數


如果當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會創建一個線程去執行這個任務;
如果當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閒線程將其取出去執行;若添加失敗(一般來說是任務緩存隊列已滿),則會嘗試創建新的線程去執行這個任務;
如果當前線程池中的線程數目達到maximumPoolSize,則會採取任務拒絕策略進行處理;
如果線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;如果允許爲核心池中的線程設置存活時間,那麼核心池中的線程空閒時間超過keepAliveTime,線程也會被終止。

------------------------------------------------------------------------------------------------------------------------------------

3.線程池中的線程初始化
默認情況下,創建線程池之後,線程池中是沒有線程的,需要提交任務之後纔會創建線程。
在實際中如果需要線程池創建之後立即創建線程,可以通過以下兩個方法辦到:
prestartCoreThread():初始化一個核心線程;
prestartAllCoreThreads():初始化所有核心線程。

4.任務緩存隊列及排隊策略
在前面我們多次提到了任務緩存隊列,即workQueue,它用來存放等待執行的任務。
workQueue的類型爲BlockingQueue<Runnable>,通常可以取下面三種類型:
1)ArrayBlockingQueue:基於數組的先進先出隊列,此隊列創建時必須指定大小;
2)LinkedBlockingQueue:基於鏈表的先進先出隊列,如果創建時沒有指定此隊列大小,則默認爲Integer.MAX_VALUE;
3)synchronousQueue:這個隊列比較特殊,它不會保存提交的任務,而是將直接新建一個線程來執行新來的任務。


5.任務拒絕策略
當線程池的任務緩存隊列已滿並且線程池中的線程數目達到maximumPoolSize,如果還有任務到來就會採取任務拒絕策略,通常有以下四種策略:ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務


6.線程池的關閉
  ThreadPoolExecutor提供了兩個方法,用於線程池的關閉,分別是shutdown()和shutdownNow(),其中:

   shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完後才終止,但再也不會接受新的任務
   shutdownNow():立即終止線程池,並嘗試打斷正在執行的任務,並且清空任務緩存隊列,返回尚未執行的任務

7.線程池容量的動態調整

  ThreadPoolExecutor提供了動態調整線程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

   setCorePoolSize:設置核心池大小
   setMaximumPoolSize:設置線程池最大能創建的線程數目大小

  當上述參數從小變大時,ThreadPoolExecutor進行線程賦值,還可能立即創建新的線程來執行任務。


----------------------------------------------------------------------------------------------------------------------------
例子:
public class Test {
    public static void main(String[] args) {  
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(5));
         
        for(int i=0;i<15;i++){
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);
            System.out.println("線程池中線程數目:"+executor.getPoolSize()+",隊列中等待執行的任務數目:"+
            executor.getQueue().size()+",已執行玩別的任務數目:"+executor.getCompletedTaskCount());
        }
        executor.shutdown();
    }
}
class MyTask implements Runnable {
   private int taskNum;
   
   public MyTask(int num) {
       this.taskNum = num;
   }
   
   @Override
   public void run() {
       System.out.println("正在執行task "+taskNum);
       try {
           Thread.currentThread().sleep(4000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("task "+taskNum+"執行完畢");
   }
}

從執行結果可以看出,當線程池中線程的數目大於5時,便將任務放入任務緩存隊列裏面,當任務緩存隊列滿了之後,便創建新的線程。如果上面程序中,將for循環中改成執行20個任務,就會拋出任務拒絕異常了。

不過在java doc中,並不提倡我們直接使用ThreadPoolExecutor,而是使用Executors類中提供的幾個靜態方法來創建線程池:
Executors.newCachedThreadPool();        //創建一個緩衝池,緩衝池容量大小爲Integer.MAX_VALUE
Executors.newSingleThreadExecutor();   //創建容量爲1的緩衝池
Executors.newFixedThreadPool(int);    //創建固定容量大小的緩衝池

下面是這三個靜態方法的具體實現;
public static ExecutorService newFixedThreadPool(int nThreads) {
   return new ThreadPoolExecutor(nThreads, nThreads,
                                 0L, TimeUnit.MILLISECONDS,
                                 new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
   return new FinalizableDelegatedExecutorService
       (new ThreadPoolExecutor(1, 1,
                               0L, TimeUnit.MILLISECONDS,
                               new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
   return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                 60L, TimeUnit.SECONDS,
                                 new SynchronousQueue<Runnable>());
}

從它們的具體實現來看,它們實際上也是調用了ThreadPoolExecutor,只不過參數都已配置好了。
newFixedThreadPool創建的線程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;
newSingleThreadExecutor將corePoolSize和maximumPoolSize都設置爲1,也使用的LinkedBlockingQueue;
newCachedThreadPool將corePoolSize設置爲0,將maximumPoolSize設置爲Integer.MAX_VALUE,使用的SynchronousQueue,也就是說來了任務就創建線程運行,當線程空閒超過60秒,就銷燬線程。
實際中,如果Executors提供的三個靜態方法能滿足要求,就儘量使用它提供的三個方法,因爲自己去手動配置ThreadPoolExecutor的參數有點麻煩,要根據實際任務的類型和數量來進行配置。
另外,如果ThreadPoolExecutor達不到要求,可以自己繼承ThreadPoolExecutor類進行重寫。


-------------如何合理配置線程池的大小-------------------------------------------------------------------------------------------------
一般需要根據任務的類型來配置線程池大小:
如果是CPU密集型任務,就需要儘量壓榨CPU,參考值可以設爲 NCPU+1
如果是IO密集型任務,參考值可以設置爲2*NCPU
當然,這只是一個參考值,具體的設置還需要根據實際情況進行調整,比如可以先將線程池大小設置爲參考值,再觀察任務運行情況和系統負載、資源利用率來進行適當調整。


一般說來,大家認爲線程池的大小經驗值應該這樣設置:(其中N爲CPU的個數)
如果是CPU密集型應用,則線程池大小設置爲N+1。
如果是IO密集型應用,則線程池大小設置爲2N+1。
如果一臺服務器上只部署這一個應用並且只有這一個線程池,那麼這種估算或許合理,具體還需自行測試驗證。
但是,IO優化中,這樣的估算公式可能更適合:最佳線程數目 = ((線程等待時間+線程CPU時間)/線程CPU時間 )* CPU數目。
因爲很顯然,線程等待時間所佔比例越高,需要越多線程。線程CPU時間所佔比例越高,需要越少線程。
下面舉個例子:比如平均每個線程CPU運行時間爲0.5s,而線程等待時間(非CPU運行時間,比如IO)爲1.5s,CPU核心數爲8,那麼根據上面這個公式估算得到:((0.5+1.5)/0.5)*8=32。
這個公式進一步轉化爲:最佳線程數目 = (線程等待時間與線程CPU時間之比 + 1)* CPU數目。


import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
* 線程池。
* 對象池:一塊內存區域,存放了一堆對象。用的時候從池中獲取,使用完畢之後放回去。
* 好處:避免反覆的創建銷燬對象。
*
* 線程池:避免反覆的創建和銷燬線程。簡化對任務的操作。
* 在jdk1.5 出現的。
*
* 線程池主要處理的任務類型有:Runnable 的命令 +  Callable 的任務。
* @author yhl
*
*/
public class ThreadPoolTest {
       static int counter = 0;

       public static void main(String[] args) throws InterruptedException, ExecutionException {
               test2();
       }
       
       static void test1(){
               //創建線程池對象。
               //創建只有一個線程的線程池
//                ExecutorService pool = Executors.newSingleThreadExecutor();
               //創建指定數量的線程的線程池
//                ExecutorService pool = Executors.newFixedThreadPool(10);
               //創建線程數量可以自動增長的線程池
               ExecutorService pool = Executors.newCachedThreadPool();
               
               //給線程池的線程加任務
               for(int i=0;i<10;i++){
                       pool.execute(new Runnable() {
                               @Override
                               public void run() {
                                       System.out.println("任務開始了!--" + ++counter);
                                       try {
                                               Thread.sleep(1000);
                                       } catch (InterruptedException e) {
                                               e.printStackTrace();
                                       }
                                       System.out.println("任務結束了!--" + counter);
                               }
                       });
               }
               
               //關閉線程池
               pool.shutdown();
       }
       
       
       static void test2() throws InterruptedException, ExecutionException{
               //創建線程池對象。
               //創建只有一個線程的線程池
               ExecutorService pool = Executors.newSingleThreadExecutor();
               //創建指定數量的線程的線程池
//                ExecutorService pool = Executors.newFixedThreadPool(10);
               //創建線程數量可以自動增長的線程池
//                ExecutorService pool = Executors.newCachedThreadPool();
               ArrayList<Future<Integer>> futures = new ArrayList<>();
               //給線程池的線程加任務
               for(int i=0;i<10;i++){
                       Future<Integer> future = pool.submit(new Callable<Integer>() {
                               Random random = new Random();
                               @Override
                               public Integer call() throws Exception {
                                       System.out.println(Thread.currentThread().getName() + "任務開始了!");
                                       return random.nextInt(100);
                               }
                       });
                       futures.add(future);
               }
               
               for (Future<Integer> future : futures) {
                       System.out.println(future.get());
               }
               
               //關閉線程池
               pool.shutdown();
       }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章