线程池原理分析和锁的深入化

1.线程池是什么

java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池,在开发过程中,合理地使用线程池能够带来很多好处

降低资源消耗,提高响应速度,提高线程的可管理性。

线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少创建和销毁线程所需的时间,从而提高效率。

2.线程池分类

ThreadPollExecutor

Executor框架的最顶层实现是ThreadPoolExecutor类,Executors工厂类中提供的newScheduledThreadPool,newFixedThreadPool,newCachedThreadPool方法其实也只是ThreadPoolExecutor的构造函数参数不同而已,通过传入不同的参数,就可以构造出不同应用场景下的线程池。

CorePoolSize:核心池的大小,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目到达corePoolSize后,就会把到达的任务放到缓存队列当中。

maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程。

keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。

unit:参数keepAliveTime的时间单位,有7中取值,在TimeUnit类中有7中静态属性。

java通过Executors提供四中线程池

newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。

newCachedThreadPool

public class ThreadDemo {
	
	public static void main(String[] args) throws Exception {
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		for(int i = 0; i<100; i++) {
			int tem = i;
			cachedThreadPool.execute(new Runnable() {
				public void run() {
					System.out.println(Thread.currentThread().getName() + ":" + tem);
				}
			});
		}
	}
	
}

线程池为无限大,当执行第二个线程任务时第一个线程任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

newFixedThreadPool

public class ThreadDemo {
	
	public static void main(String[] args) throws Exception {
		ExecutorService executorService = Executors.newFixedThreadPool(3);
		while(true) {
			executorService.execute(new Runnable() {				
				public void run() {
					System.out.println(Thread.currentThread().getName() + ":执行了。。。。。。");
				}
			});
		}
	}
	
}

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool

public class ThreadDemo {
	
	public static void main(String[] args) throws Exception {
		ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
		while(true) {
			scheduledThreadPool.schedule(new Runnable() {
				public void run() {
					System.out.println(Thread.currentThread().getName());
				}
			}, 3,TimeUnit.SECONDS);
		}
	}
	
}

创建一个定长线程池,支持定时及周期性任务执行,延迟3秒执行。

newSingleThreadExecutor

public class ThreadDemo {
	
	public static void main(String[] args) throws Exception {
		ExecutorService executorService  = Executors.newSingleThreadExecutor();
		for(int i=0; i<10; i++) {
			int tem = i;
			executorService.execute(new Runnable() {
				public void run() {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + ":" + tem);
				}
			});
		}
		
	}
	
}

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。

3.线程池原理

提交一个任务到线程池中,线程池的处理流程如下:

1.判断线程池里的核心线程释放都在执行任务,如果不是则创建一个新的工作线程来执行任务,如果核心线程都在执行任务,则进入下个流程。

2.线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里,如果工作队列满了,则进入下个流程。

3.判断线程池里的线程释放都处于工作状态,如果没有,则创建一个新的工作线程来执行任务,如果已经满了,则交给饱和策略来处理这个任务。

合理配置线程池

cpu密集时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得美国线程都在执行任务

IO密集时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数。

5.手写线程池

public class Demo {
	public static void main(String[] args) {
		MyThreadPool m = new MyThreadPool(5);
		Runnable r = new Runnable() {
			public void run() {
				System.out.println("线程执行。。。。。");
			}
		};
		for(int i =0 ;i <30; i++) {
			m.execute(r);
		}
		
		
	}
}

class MyThreadPool {
	//定义一个集合存储线程
	private ArrayList<Thread> threads;
	//定义一个阻塞式队列
	private ArrayBlockingQueue<Runnable> arrayBlockingQueue;
	//线程池最大线程数
	private int maxThreadCount;
	//已经工作的线程数
	private int jobThreadCount;
	
	private ReentrantLock lock = new ReentrantLock();
	
	public MyThreadPool(int maxThreadCount) {
		threads = new ArrayList<Thread>();
		this.maxThreadCount = maxThreadCount;
		//队列中的线程数是集合中的线程2倍
		arrayBlockingQueue = new ArrayBlockingQueue<>(this.maxThreadCount * 2);
		jobThreadCount = 0;
	}
	
	public void execute(Runnable runnable) {
		try {
			lock.lock();
			//判断线程池中的线程是否已满
			if(maxThreadCount > jobThreadCount) {
				Thread t = new Thread(runnable);
				t.start();
				threads.add(t);
				jobThreadCount++;
			}else {
				//线程池已满放入队列中
				if(!arrayBlockingQueue.offer(runnable)) {
					System.out.println("队列已满");
				}
			}
		} catch (Exception e) {
			
		}finally {
			lock.unlock();
		}
	}
}

6.java锁的深度化

悲观锁:悲观锁悲观的认为每一次操作都会造成更新丢失问题,在每次查询时加上排他锁。

乐观锁:乐观锁会乐观的认为每次查询都不会造成更新丢失,利用版本字段控制。

重入锁:也叫递归锁,指的是同一线程,外层函数获取锁之后,内层递归函数依然有获取该锁的代码。

读写锁:两个线程同时读一个资源没有任何问题,所有应该允许多个线程能在同时读取共享资源,但是如果有一个线程想去写这些共享资源,就不应该有其他线程对该资源进行读或写。

CAS无锁机制:它包含三个参数CAS(v,e,n)  V表示要感谢的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做,最后,CAS返回当前V的真实值。

自旋锁:自旋锁是采用让当前线程不停地在循环体内执行实现的,当循环的条件被其他线程改变时,才能进入临界区。

猜你喜欢

转载自blog.csdn.net/qq_38065439/article/details/87431680