线程池

线程池

一、ExecutorService接口
  常用的实现类:ThreadPoolExecutor,参数:
  corePoolSize - 核心线程数,池中所保存的线程数,包括空闲线程。
  maximumPoolSize - 池中允许的最大线程数=核心线程数+临时线程数。
  keepAliveTime - 临时线程等待新任务的最长时间。
  unit - keepAliveTime 参数的时间单位。
  workQueue - 阻塞队列。
  handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

BlockingQueue<Runnable> que = new ArrayBlockingQueue<Runnable>(5);
		ExecutorService ex=new
		ThreadPoolExecutor(5,10,5,TimeUnit.SECONDS,
				que,new RejectedExecutionHandler(){
					//当有处理不了的线程时进入该方法
					public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
						System.out.println("线程池和阻塞队列已经全被使用");
					}
		});
ExecutorService threadPool = Executors.newCachedThreadPool();
new ThreadPoolExecutor(0, Integer.MAX_VALUE,
  60L, TimeUnit.SECONDS,
  new SynchronousQueue<Runnable>())

没有核心线程,临时线程无限大,存活时间是60s,同步队列(只能容纳一个线程)
  大池子小队列:优点(适用场景):适用于高访问量、高并发的场景(短请求)。用户请求不需要等待排队,能够快速响应用户请求。隐患:可能会出现线程的频繁创建和销毁。此外,如果用户的请求都是长请求,可能会发生内存溢出的场景。

ExecutorService threadPool = Executors.newFixedThreadPool(5);
new ThreadPoolExecutor(nThreads, nThreads,
  0L, TimeUnit.MILLISECONDS,
  new LinkedBlockingQueue<Runnable>());

核心线程=最大线程数 ,没有临时线程,阻塞队列是无限的,小池子大队列,优点:能够降低服务器的压力。缺点:用户请求的响应时间较长。
二、线程池的实现原理
  1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
  2、判断线程池工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
  3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
三、代码分析具体线程池工作原理

	LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(5);
	ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5,10,60,TimeUnit.SECONDS,queue);
 	for (int i = 0;i < 16;i++) {
 		threadPool.excute(new Thread(new Runnable(){
 			Thread.sleep(300);
 		}))
 		System.out.println("线程池中活跃的线程数:"+threadPool.getPoolSize());
 	}
 	if (queue.size() > 0) {
 		System.out.println("队列中阻塞的线程数" + queue.size())
 	}
 	threadPool.shutdown();

1、创建的线程池具体配置为:核心线程数量为5个;全部线程数量为10个;工作队列的长度为5。
  2、我们通过queue.size()的方法来获取工作队列中的任务数。
  3、运行原理:
   刚开始都是在创建新的线程,达到核心线程数量5个后,新的任务进来后不再创建新的线程,而是将任务加入工作队列,任务队列到达上线5个后,新的任务又会创建新的普通线程,直到达到线程池最大的线程数量10个,后面的任务则根据配置的饱和策略来处理。我们这里没有具体配置,使用的是默认的配置AbortPolicy:直接抛出异常。

四、RejectedExecutionHandler:饱和策略
  当队列和线程池都满了,说明线程池处于饱和状态,那么必须对新提交的任务采用一种特殊的策略来进行处理。这个策略默认配置是AbortPolicy,表示无法处理新的任务而抛出异常。JAVA提供了4中策略:
  1、AbortPolicy:直接抛出异常
  2、CallerRunsPolicy:只用调用所在的线程运行任务
  3、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  4、DiscardPolicy:不处理,丢弃掉。
  设置策略有两种方式:

//1
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler);

//2
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue);
  threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());

五、Callable
  Callable是一个接口,一个类只要实现了该接口,就是一个线程类。jdk1.5引入的和Runnable比较:1、run()不能抛出异常,但是call()可以。2、run()没有返回值类型,而call()方法可以,返回值的类型根据实现接口时Callable的类型来决定。call()方法执行返回值使用Future接收,调用get()方法获取返回值。注意:Callable只能通过线程池来启动。
  ExecutorService es = Executors.newCachedThreadPool();
  Future future= es.submit(new CallRunn());//Future对象可以获取call()返回值,可以根据返回值做相关的业务处理。
  future.get()方法会阻塞当前线程, 直到任务执行完成返回结果为止。
   future.cancel(true) //将任务提前完成
   future.isCancelled() //判断任务的执行状态
   future.isDone() //判断任务是否已经执行完
六、ReentrantLock重入锁
  重入锁性能较高,原因是默认使用的是非公平锁的策略。
  Lock lock = new ReentrantLock();
  ReentrantLock(false):非公平锁,默认的。ReentrantLock(true):公平锁。强调:使用重入锁,一定要注意锁的释放问题,代码中使用try{}catch(){}的时候,要将锁的释放放到finally中。补充:同步代码块锁释放的问题JVM帮我们处理
  重入锁底层使用原理:假如运算1s,唤醒2s,线程的唤醒时间想比较线程的运算时间长,1次 用掉1s,唤醒2s,1次 用掉1s,吞吐量:2次/4s,公平锁性能低的原因在排队机制。1次用掉1s,1次用掉1s,1次用掉1s,1次用掉1s,吞吐量:4次/4s–非公平锁
七、读写锁ReadWriteLock
  ReadWriteLock lock = new ReentrantReadWriteLock();
  读写锁还是很实用的,因为一般场景下,数据的并发操作都是读多于写,在这种情况下,读写锁能够提供比排它锁更好的并发性。
  一个线程要想同时持有写锁和读锁,必须先获取写锁再获取读锁;写锁可以“降级”为读锁;读锁不能“升级”为写锁。
八、原子性
  AtomicInteger num = new AtomicInteger(0);
九、CAS
  CAS,在Java并发应用中通常指CompareAndSwap或CompareAndSet,即比较并交换。
  参考这篇博客,对于CAS讲的挺详细的:https://blog.csdn.net/mmoren/article/details/79185862
十、Thread
  1、start方法:启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
  2、run方法:run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
  3、sleep方法:1) sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。2) 如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread类的静态sleep()方法来实现。3) 当当前线程调用sleep()方法进入阻塞状态后,在其睡眠时间内,该线程不会获得执行机会,即使系统中没有其他可执行线程,处于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行。4) 但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
  4、yield方法:1) yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入到就绪状态。即让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。2)调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,当某个线程调用了yield()方法之后,只有优先级与当前线程相同或者比当前线程更高的处于就绪状态的线程才会获得执行机会。3)注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
  5、join方法:假如在main线程中,调用thread.join方法,则main方法会等待thread线程执行完毕或者等待一定的时间。如果调用的是无参join方法,则等待thread执行完毕,如果调用的是指定了时间参数的join方法,则等待一定的时间。
  6、interrupt方法:interrupt,顾名思义,即中断的意思。单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就说,它可以用来中断一个正处于阻塞状态的线程;另外,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。调用interrupt方法不能中断正在运行中的线程,配合isInterrupted()可以中断正在运行的线程。一般不这么多,一般在外面定义一个volatile的变量来控制线程的结束。
  7、interrupted方法:interrupted()函数是Thread静态方法,用来检测当前线程的interrupt状态,检测完成后,状态清空。
  8、Thread的方法
   1)getId 用来得到线程ID
   2)getName和setName 用来得到或者设置线程名称。
   3)getPriority和setPriority 用来获取和设置线程优先级。
   4)setDaemon和isDaemon 用来设置线程是否成为守护线程和判断线程是否是守护线程。
   守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。
   5)currentThread() 用来获取当前线程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值