Java學習——併發編程之線程池原理分析

四、線程池原理分析

1.阻塞隊列與非阻塞隊列

1.1阻塞隊列與非阻塞隊列的區別:

(1)從空的阻塞隊列中讀取元素,將會阻塞,知道其他線程插入元素到這個隊列中。

(2)往滿的隊列中添加元素,同樣也會阻塞,知道有線程從隊列中取出元素或者隊列中的元素被清除。

1.2下面列舉幾個常見的隊列:

(1)ArrayDeque, (數組雙端隊列) 
(2)PriorityQueue, (優先級隊列) 
(3)ConcurrentLinkedQueue, (基於鏈表的併發隊列) 
(4)DelayQueue, (延期阻塞隊列)(阻塞隊列實現了BlockingQueue接口) 
(5)ArrayBlockingQueue, (基於數組的併發阻塞隊列) 
(6)LinkedBlockingQueue, (基於鏈表的FIFO阻塞隊列) 
(7)LinkedBlockingDeque, (基於鏈表的FIFO雙端阻塞隊列) 
(8)PriorityBlockingQueue, (帶優先級的無界阻塞隊列) 
(9)SynchronousQueue (併發同步阻塞隊列)

1.3ConcurrentLinkedQueue

ConcurrentLinkedQueue :是一個基於高併發場景下的隊列,通過無鎖的方式實現了高併發下的高性能。他是一個聚集鏈接節點的無界安全隊列。不允許有null元素。

ConcurrentLinkedQueue重要方法:
add 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中這倆個方法沒有任何區別)
poll() 和peek() 都是取頭元素節點,區別在於前者會刪除元素,後者不會。

/**	
 * 
 * @author johson
 *阻塞隊列和非阻塞隊列
 *阻塞隊列的最大好處就是能防止隊列容器溢出,防止數據丟失
 */
public class test01 {
	
	public static void main(String[] args){
	
	//新建一個非阻塞式隊列,無界的
	ConcurrentLinkedQueue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<>();
	
	concurrentLinkedQueue.add("張三"); //add()調用了offer方法
	
	//concurrentLinkedQueue.offer("王麻子");
	
	concurrentLinkedQueue.add("李四");
	
	concurrentLinkedQueue.add("王五");
	
	System.out.println("peek()方法showtime");
	//peek()是獲取但不刪除
	System.out.println(concurrentLinkedQueue.peek());
	System.out.println(concurrentLinkedQueue.peek());
	System.out.println(concurrentLinkedQueue.peek());
	
	System.out.println("pool()方法showtime");
	//一次性只能獲取一個值,poll()是獲取一個刪除一個
	System.out.println(concurrentLinkedQueue.poll());
	System.out.println(concurrentLinkedQueue.poll());
	System.out.println(concurrentLinkedQueue.poll());
	

	
	System.out.println(concurrentLinkedQueue.size());
	
	}
}

peek()方法與poll()方法運行對比:

1.4BlockingQueue

阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。這兩個附加的操作是:

(1)在隊列爲空時,獲取元素的線程會等待隊列變爲非空。

(2)當隊列滿時,存儲元素的線程會等待隊列可用。

阻塞隊列常用於生產者和消費者的場景,生產者是往隊列裏添加元素的線程,消費者是從隊列裏拿元素的線程。阻塞隊列就是生產者存放元素的容器,而消費者也只從容器裏拿元素。

阻塞隊列的簡單實驗:

/**	
 * 
 * @author johson
 * 有界隊列,阻塞隊列
 */
public class test02 {
	//阻塞式隊列:存隊列的時候,如果滿了。就會等待;取隊列的時候,如果沒有值或者取不到值也會等待
	public static void main(String[] args) throws InterruptedException{
	
		//新建一個大小爲3的阻塞隊列
		BlockingQueue<String> blockingDeque = new ArrayBlockingQueue<String>(3);
		//添加非阻塞隊列
		blockingDeque.offer("張三");
		//添加阻塞式隊列,如果隊列已經滿了,就會等待3秒,如果還是滿的就會結束插入操作
		blockingDeque.offer("李四",3,TimeUnit.SECONDS);
		
		System.out.println(blockingDeque.poll());
		System.out.println(blockingDeque.poll(3,TimeUnit.SECONDS));
		//獲取阻塞式隊列,過了三秒沒有獲取到就不再等待
		System.out.println(blockingDeque.poll(3,TimeUnit.SECONDS));
	
	}
}

利用BlockingQueue實現生產者與消費者實驗:

/**
 * 生產者線程
 * @author johson
 *
 */
class ProduceThread implements Runnable{
	
	private BlockingQueue<String> blockingQueue;
	
	private volatile boolean flag = true;
	//計數器,原子類
	AtomicInteger atomicInteger = new AtomicInteger();
	
	public ProduceThread(BlockingQueue<String> blockingQueue) {
		this.blockingQueue = blockingQueue;
	}
	
	@Override
	public void run() {
		System.out.println("生產者線程啓動");
		try {
			while(flag){
				//基於線程安全的一個自增方法
				String data = atomicInteger.incrementAndGet()+"";
				boolean offer = blockingQueue.offer(data,2,TimeUnit.SECONDS);
				if(offer){
					System.out.println("生產者入列成功,data=" + data);
				}
				else {
					System.out.println("生產者入列失敗,data=" + data);
				}
				
				Thread.sleep(1000);
			}			
		} catch (InterruptedException e) {
				e.printStackTrace();
		}
	}
	//停止方法
	public void stop(){
		this.flag  =false;
	}
}

/**
 * 消費者線程
 * @author johson
 *
 */
class ConsumerThread implements Runnable{
	
	private BlockingQueue<String> blockingQueue;
	
	private volatile boolean flag = true;
	
	public ConsumerThread(BlockingQueue<String> blockingQueue) {
		this.blockingQueue = blockingQueue;
	}
	@Override
	public void run() {
		try {
			while (flag) {
				String data = blockingQueue.poll(2, TimeUnit.SECONDS);	
				if(data == null){
					System.out.println("消費者超過2秒時間沒有獲取到信息");
					flag = true;
					return ;
				}
				System.out.println("消費者獲取到data="+data);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			System.out.println("消費者已經停止");
		}
	}

}

public class test03 {

	public static void main(String[] args) {
		BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<String>(10);
		ProduceThread produceThread = new ProduceThread(blockingQueue);
		
		ConsumerThread consumerThread = new ConsumerThread(blockingQueue);
		
		Thread t1 = new Thread(produceThread);
		
		Thread t2 = new Thread(consumerThread);
		
		t1.start();
		
		t2.start();
		
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		produceThread.stop();
		
	}

}

實驗結果:

2.線程池

2.1線程池的作用

線程池是爲了突然大量爆發的線程而設計的,通過有限的幾個固定線程爲大量的操所服務,減少了線程創建和銷燬的時間,從而提高效率。

2.2線程池能夠帶來3個好處


第一:降低資源消耗。通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。複用原來的線程(但是run()方法體不一樣了)
第三:提高線程的可管理性。線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控。但是,要做到合理利用。

2.3線程池四種創建方式

Java通過Executors(jdk1.5併發包)提供四種線程池,分別爲:

(1)newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。

/**
 * newCachedThreadPool創建一個可緩存線程池
 * @author johson
 *
 */
public class test04 {

	public static void main(String[] args) {
		
		//創建了一個可緩存的線程池重複利用
		ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

		for(int i = 0;i < 20;i++){
			final int t = i;
			newCachedThreadPool.execute(new Runnable() {
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName()+" "+t);
				}
			});
		}

	}

}

(2)newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。

/**
 * newFixedThreadPool 創建一個定長線程池
 * @author johson
 *
 */
public class test05 {
	
	public static void main(String[] args) {
		ExecutorService newCachedThreadPool = Executors.newFixedThreadPool(3);
		
		for(int i = 0;i < 20;i++){
			final int t = i;
			newCachedThreadPool.execute(new Runnable() {
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName()+" "+t);
				}
			});
		}

	}

}

(3)newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。


/**
 * newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
 * @author johson
 *
 */
public class test06 {
	
	public static void main(String[] args) {
		ScheduledExecutorService newCachedThreadPool = Executors.newScheduledThreadPool (10);
		
		for(int i = 0;i < 20;i++){
			final int t = i;
			newCachedThreadPool.schedule(new Runnable() {
				
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName()+" "+t);
					
				}
			},3,TimeUnit.SECONDS);//3秒後執行調度
/*			newCachedThreadPool.execute(new Runnable() {
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName()+" "+t);
				}
			});*/
		}

	}

}

(4)newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

/**
 * newSingleThreadExecutor 創建一個單線程化的線程池
 * @author johson
 *
 */
public class test07 {

	public static void main(String[] args) {
		ExecutorService newCachedThreadPool = Executors.newSingleThreadExecutor();
		
		for(int i = 0;i < 10;i++){
			final int t = i;
			newCachedThreadPool.execute(new Runnable() {
				
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName()+" "+t);
				}
			});

		}
	}

}

2.4線程池原理分析

常用的四個線程池都是通過ThreadPoolExecutor()封裝的

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

corePoolSize:核心線程數

maximumPoolSize:最大線程數

核心線程數與最大線程數的區別:核心線程數是指實際用的線程數,最大線程數是指最多能創建的線程數

最多支持的線程數=最大線程數+隊列長度

2.5自定義線程池


/**
 * 自定義線程池
 * @author johson
 *
 */

class TaskThread implements Runnable{
	
	private String threadName;
	
	public TaskThread(String threadName) {
		this.threadName = threadName;
	}
	
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName()+" "+threadName);
		
	}
}
public class test08 {
	
	public static void main(String[] args) {
		
		//核心線程數爲1(最多運行的線程數),最大線程數爲2(最多隻能創建幾個線程),線程空閒超時時間,分鐘還是秒,隊列爲3個
		ThreadPoolExecutor executor = new ThreadPoolExecutor(
				1, 2, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3));
		
		//任務1創建線程,正在執行
		executor.execute(new TaskThread("任務1"));
		//任務2存放在隊列緩存中,因爲只有一個核心線程
		executor.execute(new TaskThread("任務2"));
		//任務3存放在隊列緩存中,因爲只有一個核心線程
		executor.execute(new TaskThread("任務3"));

		executor.execute(new TaskThread("任務4"));
		//再創建一個線程,因爲最大能創建的線程數爲2
		executor.execute(new TaskThread("任務5"));
		//任務6會報錯,拒絕策略爲線程數>最大線程數+隊列長度
		//executor.execute(new TaskThread("任務6"));
	}


}

3.合理配置線程池

3.1 IO密集

表示該任務需要大量的IO,即大量的阻塞。

3.2 CPU密集

表示該任務需要大量的運算,而沒有阻塞,CPU一直全速運行。

CPU密集任務只有在真正的多核CPU上纔可能得到加速(通過多線程),而在單核CPU上,無論你開幾個模擬的多線程,該任務都不可能得到加速,因爲CPU總的運算能力就那些。

CPU密集型時,任務可以少配置線程數,大概和機器的cpu核數相當,這樣可以使得每個線程都在執行任務

IO密集型時,大部分線程都阻塞,故需要多配置線程數,2*cpu核數

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