游戏后台实记

1.ArrayBlockingQueue

ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列。
线程安全是指,ArrayBlockingQueue内部通过“互斥锁”保护竞争资源,实现了多线程对竞争资源的互斥访问。而有界,则是指ArrayBlockingQueue对应的数组是有界限的。 阻塞队列,是指多线程访问竞争资源时,当竞争资源已被某线程获取时,其它要获取该资源的线程需要阻塞等待;而且,ArrayBlockingQueue是按 FIFO(先进先出)原则对元素进行排序,元素都是从尾部插入到队列,从头部开始返回。

注意:ArrayBlockingQueue不同于ConcurrentLinkedQueue,ArrayBlockingQueue是数组实现的,并且是有界限的;而ConcurrentLinkedQueue是链表实现的,是无界限的。

ArrayBlockingQueue的数据结构,如下图所示:

说明
    1. ArrayBlockingQueue继承于AbstractQueue,并且它实现了BlockingQueue接口。
    2. ArrayBlockingQueue内部是通过Object[]数组保存数据的,也就是说ArrayBlockingQueue本质上是通过数组实现的。ArrayBlockingQueue的大小,即数组的容量是创建ArrayBlockingQueue时指定的。
    3. ArrayBlockingQueue与ReentrantLock是组合关系,ArrayBlockingQueue中包含一个ReentrantLock对象(lock)。ReentrantLock是可重入的互斥锁,ArrayBlockingQueue就是根据该互斥锁实现“多线程对竞争资源的互斥访问”。而且,ReentrantLock分为公平锁和非公平锁,关于具体使用公平锁还是非公平锁,在创建ArrayBlockingQueue时可以指定;而且,ArrayBlockingQueue默认会使用非公平锁。
    4. ArrayBlockingQueue与Condition是组合关系,ArrayBlockingQueue中包含两个Condition对象(notEmpty和notFull)。而且,Condition又依赖于ArrayBlockingQueue而存在,通过Condition可以实现对ArrayBlockingQueue的更精确的访问 -- (01)若某线程(线程A)要取数据时,数组正好为空,则该线程会执行notEmpty.await()进行等待;当其它某个线程(线程B)向数组中插入了数据之后,会调用notEmpty.signal()唤醒“notEmpty上的等待线程”。此时,线程A会被唤醒从而得以继续运行。(02)若某线程(线程H)要插入数据时,数组已满,则该线程会它执行notFull.await()进行等待;当其它某个线程(线程I)取出数据之后,会调用notFull.signal()唤醒“notFull上的等待线程”。此时,线程H就会被唤醒从而得以继续运行。

2.定时器


import Java.util.Timer;
import java.util.TimerTask;
import java.util.Date;

/**
 * @author vincent
 */

public class TimerTest {

 public static void main(String[] args) {
  Timer t = new Timer();
  
  //在5秒之后执行TimerTask的任务
  t.schedule(new TimerTask(){
     public void run()
     {System.out.println("this is task you do1");}
    },5*1000);


  //在Date指定的特定时刻之后执行TimerTask的任务
  Date d1 = new Date(System.currentTimeMillis()+1000);
  t.schedule(new TimerTask(){
   public void run()
   {System.out.println("this is task you do2");}
  },d1);


  //在Date指定的特定时刻之后,每隔1秒执行TimerTask的任务一次
  Date d2 = new Date(System.currentTimeMillis()+1000);
  t.schedule(new TimerTask(){
   public void run()
   {System.out.println("this is task you do3");}
  },d2,1*1000);


  //在3秒之后,每隔1秒执行TimerTask的任务一次
  t.schedule(new TimerTask(){
   public void run()
   {System.out.println("this is task you do4");}
  },3*1000,1*1000);
  
  //在3秒之后,绝对每隔2秒执行TimerTask的任务一次

  t.scheduleAtFixedRate(new TimerTask(){
   public void run()
   {System.out.println("this is task you do6");}
  },3*1000,2*1000);
 }



schedule和scheduleAtFixedRate的区别在于,如果指定开始执行的时间在当前系统运行时间之前,scheduleAtFixedRate会把已经过去的时间也作为周期执行,而schedule不会把过去的时间算上。

比如

SimpleDateFormat fTime = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
  Date d1 = fTime.parse("2005/12/30 14:10:00");
  
  t.scheduleAtFixedRate(new TimerTask(){
   public void run()
   {
       System.out.println("this is task you do6");
   }
  },d1,3*60*1000);

间隔时间是3分钟,指定开始时间是2005/12/30 14:10:00,如果我在14:17:00分执行这个程序,那么会立刻打印3次

this is task you do6     //14:10
this is task you do6     //14:13
this is task you do6     //14:16

并且注意,下一次执行是在14:19 而不是 14:20。就是说是从指定的开始时间开始计时,而不是从执行时间开始计时。

但是上面如果用schedule方法,间隔时间是3分钟,指定开始时间是2005/12/30 14:10:00,那么在14:17:00分执行这个程序,则立即执行程序一次。并且下一次的执行时间是 14:20,而不是从14:10开始算的周期(14:19)。


3.java线程池excutorservice

CachedThreadPool

CachedThreadPool会创建一个缓存区,将初始化的线程缓存起来。会终止并且从缓存中移除已有60秒未被使用的线程。

如果线程有可用的,就使用之前创建好的线程,

如果线程没有可用的,就新创建线程。


  • 重用:缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse;如果没有,就建一个新的线程加入池中
  • 使用场景:缓存型池子通常用于执行一些生存期很短的异步型任务,因此在一些面向连接的daemon型SERVER中用得不多。
  • 超时:能reuse的线程,必须是timeout IDLE内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
  • 结束:注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止。

  1. // 线程池的大小会根据执行的任务数动态分配  
  2. ExecutorService cachedThreadPool = Executors.newCachedThreadPool();  
  3.   
  4. public static ExecutorService newCachedThreadPool() {  
  5.     return new ThreadPoolExecutor(0,                 //core pool size  
  6.                                   Integer.MAX_VALUE, //maximum pool size  
  7.                                   60L,               //keep alive time  
  8.                                   TimeUnit.SECONDS,  
  9.                                   new SynchronousQueue<Runnable>());  
  10. }  

执行结果:

  1. 第1次任务的第1次执行  
  2. 第4次任务的第1次执行  
  3. 第3次任务的第1次执行  
  4. 第2次任务的第1次执行  
  5. 第3次任务的第2次执行  
  6. 第4次任务的第2次执行  
  7. 第2次任务的第2次执行  
  8. 第1次任务的第2次执行  
  9. 第2次任务的第3次执行  
  10. 第4次任务的第3次执行  
  11. 第3次任务的第3次执行  
  12. 第1次任务的第3次执行  
  13. 第2次任务的第4次执行  
  14. 第1次任务的第4次执行  
  15. 第3次任务的第4次执行  
  16. 第4次任务的第4次执行  

4个任务是交替执行的


FixedThreadPool

在FixedThreadPool中,有一个固定大小的池。

如果当前需要执行的任务超过池大小,那么多出的任务处于等待状态,直到有空闲下来的线程执行任务,

如果当前需要执行的任务小于池大小,空闲的线程也不会去销毁。


  • 重用:fixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程
  • 固定数目:其独特之处在于,任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子
  • 超时:和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDP IDLE机制之类的),
  • 使用场景:所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器
  • 源码分析:从方法的源代码看,cache池和fixed 池调用的是同一个底层池,只不过参数不同:
    fixed池线程数固定,并且是0秒IDLE(无IDLE)
    cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE

  1. // 创建可以容纳3个线程的线程池  
  2. ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);  
  3.   
  4. public static ExecutorService newFixedThreadPool(int nThreads) {  
  5.         return new ThreadPoolExecutor(nThreads, //core pool size  
  6.                                       nThreads, //maximum pool size  
  7.                                       0L,       //keep alive time  
  8.                                       TimeUnit.MILLISECONDS,  
  9.                                       new LinkedBlockingQueue<Runnable>());  
  10. }  

执行结果:
  1. 第1次任务的第1次执行  
  2. 第3次任务的第1次执行  
  3. 第2次任务的第1次执行  
  4. 第3次任务的第2次执行  
  5. 第2次任务的第2次执行  
  6. 第1次任务的第2次执行  
  7. 第3次任务的第3次执行  
  8. 第1次任务的第3次执行  
  9. 第2次任务的第3次执行  
  10. 第3次任务的第4次执行  
  11. 第1次任务的第4次执行  
  12. 第2次任务的第4次执行  
  13. 第4次任务的第1次执行  
  14. 第4次任务的第2次执行  
  15. 第4次任务的第3次执行  
  16. 第4次任务的第4次执行  

创建了一个固定大小的线程池,容量为3,然后循环执行了4个任务。由输出结果可以看到, 前3个任务首先执行完,然后空闲下来的线程去执行第4个任务


SingleThreadExecutor 

SingleThreadExecutor得到的是一个单个的线程,这个线程会保证你的任务执行完成。

如果当前线程意外终止,会创建一个新线程继续执行任务,这和我们直接创建线程不同,也和newFixedThreadPool(1)不同。

  1. // 创建单个线程的线程池,如果当前线程在执行任务时突然中断,则会创建一个新的线程替代它继续执行任务    
  2. ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();  
  3.   
  4. public static ExecutorService newSingleThreadExecutor() {  
  5.         return new FinalizableDelegatedExecutorService  
  6.             (new ThreadPoolExecutor(1,  //core pool size  
  7.                                     1,  //maximum pool size  
  8.                                     0L, //keep alive time  
  9.                                     TimeUnit.MILLISECONDS,  
  10.                                     new LinkedBlockingQueue<Runnable>()));  
  11. }  

执行结果:

  1. 第1次任务的第1次执行  
  2. 第1次任务的第2次执行  
  3. 第1次任务的第3次执行  
  4. 第1次任务的第4次执行  
  5. 第2次任务的第1次执行  
  6. 第2次任务的第2次执行  
  7. 第2次任务的第3次执行  
  8. 第2次任务的第4次执行  
  9. 第3次任务的第1次执行  
  10. 第3次任务的第2次执行  
  11. 第3次任务的第3次执行  
  12. 第3次任务的第4次执行  
  13. 第4次任务的第1次执行  
  14. 第4次任务的第2次执行  
  15. 第4次任务的第3次执行  
  16. 第4次任务的第4次执行  

4个任务是顺序执行的



ScheduledThreadPool

ScheduledThreadPool是一个固定大小的线程池,与FixedThreadPool类似,执行的任务是定时执行

  1. // 效果类似于Timer定时器  
  2. ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);  
  3.   
  4. public ScheduledThreadPoolExecutor(int corePoolSize) {  
  5.         super(corePoolSize,      //core pool size  
  6.               Integer.MAX_VALUE, //maximum pool size  
  7.               0,                 //keep alive time  
  8.               TimeUnit.NANOSECONDS,  
  9.               new DelayedWorkQueue());  
  10. }  

执行结果:

  1. 第1次任务的第1次执行  
  2. 第2次任务的第1次执行  
  3. 第3次任务的第1次执行  
  4. 第2次任务的第2次执行  
  5. 第1次任务的第2次执行  
  6. 第3次任务的第2次执行  
  7. 第2次任务的第3次执行  
  8. 第1次任务的第3次执行  
  9. 第3次任务的第3次执行  
  10. 第2次任务的第4次执行  
  11. 第1次任务的第4次执行  
  12. 第3次任务的第4次执行  
  13. 第4次任务的第1次执行  
  14. 第4次任务的第2次执行  
  15. 第4次任务的第3次执行  
  16. 第4次任务的第4次执行  


  druid线程池==项目启动时间较久时报  abandon connection, open stackTrace错误;https://my.oschina.net/haogrgr/blog/224010修改removeabonden参数




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值