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 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()方法运行对比:

Java学习——并发编程之线程池原理分析_第1张图片

1.4BlockingQueue

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:

(1)在队列为空时,获取元素的线程会等待队列变为非空。

(2)当队列满时,存储元素的线程会等待队列可用。

阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

阻塞队列的简单实验:

/**	
 * 
 * @author johson
 * 有界队列,阻塞队列
 */
public class test02 {
	//阻塞式队列:存队列的时候,如果满了。就会等待;取队列的时候,如果没有值或者取不到值也会等待
	public static void main(String[] args) throws InterruptedException{
	
		//新建一个大小为3的阻塞队列
		BlockingQueue blockingDeque = new ArrayBlockingQueue(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 blockingQueue;
	
	private volatile boolean flag = true;
	//计数器,原子类
	AtomicInteger atomicInteger = new AtomicInteger();
	
	public ProduceThread(BlockingQueue 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 blockingQueue;
	
	private volatile boolean flag = true;
	
	public ConsumerThread(BlockingQueue 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 blockingQueue = new LinkedBlockingQueue(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();
		
	}

}

实验结果:

Java学习——并发编程之线程池原理分析_第2张图片

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 workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

corePoolSize:核心线程数

maximumPoolSize:最大线程数

核心线程数与最大线程数的区别:核心线程数是指实际用的线程数,最大线程数是指最多能创建的线程数

最多支持的线程数=最大线程数+队列长度

Java学习——并发编程之线程池原理分析_第3张图片

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(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核数

你可能感兴趣的:(JAVA,多线程,基础)