线程池和定时器

Java笔试面试题整理第六波(修正版) 1、线程池ThreadPool相关 在java.util.concurrent包下,提供了一系列与线程池相关的类。合理的使用线程池,可以带来多个好处: (1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗; (2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行; (3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低 阅读详情


1、线程池ThreadPool相关

在java.util.concurrent包下,提供了一系列与线程池相关的类。合理的使用线程池,可以带来多个好处:
(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行;
(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池可以应对突然大爆发量的访问,通过有限个固定线程为大量的操作服务,减少创建和销毁线程所需的时间。

与线程执行、线程池相关类的关系如图:

我们一般通过工具类Executors的静态方法(如newFixedThreadPool())来获取ThreadPoolExecutor线程池或静态方法(如newScheduledThreadPool())来获取ScheduleThreadPoolExecutor线程池。如下使用:
ExecutorService threadpool= Executors.newFixedThreadPool(10);
我们指定了获取10个数量的固定线程池,Executors中有很多重载的获取线程池的方法,比如可以通过自定义的ThreadFactory来为每个创建出来的Thread设置更为有意义的名称。Executors创建线程池的方法内部也就是new出新的ThreadPoolExecutor或ScheduleThreadPoolExecutor,给我们配置了很多默认的设置。如下:
[java]  view plain  copy
  1. public static ExecutorService newFixedThreadPool(int nThreads) {  
  2.         return new ThreadPoolExecutor(nThreads, nThreads,  
  3.                                       0L, TimeUnit.MILLISECONDS,  
  4.                                       new LinkedBlockingQueue<Runnable>());  
  5.     }  
上面通过ThreadPoolExecutor的构造方法,为我们创建了一个线程池,很多参数Executors工具类自动为我们配置好了。创建一个ThreadPoolExecutor线程池一般需要以下几个参数:
[java]  view plain  copy
  1. public ThreadPoolExecutor(int corePoolSize,  
  2.                               int maximumPoolSize,  
  3.                               long keepAliveTime,  
  4.                               TimeUnit unit,  
  5.                               BlockingQueue<Runnable> workQueue,  
  6.                               ThreadFactory threadFactory,  
  7.                               RejectedExecutionHandler handler)   
(1)corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
(2)maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。
(3)keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。
(4)TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS)等。
(5)workQueue(任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue
(6)threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
(7)handler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。

我们尽量优先使用Executors提供的静态方法来创建线程池,如果Executors提供的方法无法满足要求,再自己通过ThreadPoolExecutor类来创建线程池。

提交任务的两种方式:
(1)通过execute()方法,如:
[java]  view plain  copy
  1. ExecutorService threadpool= Executors.newFixedThreadPool(10);  
  2. threadpool.execute(new Runnable(){...});  
这种方式提交没有返回值,也就不能判断任务是否被线程池执行成功。

(2)通过submit()方法,如:     
[java]  view plain  copy
  1. Future<?> future = threadpool.submit(new Runnable(){...});  
  2.     try {  
  3.             Object res = future.get();  
  4.         } catch (InterruptedException e) {  
  5.             // 处理中断异常  
  6.             e.printStackTrace();  
  7.         } catch (ExecutionException e) {  
  8.             // 处理无法执行任务异常  
  9.             e.printStackTrace();  
  10.         }finally{  
  11.             // 关闭线程池  
  12.             executor.shutdown();  
  13.         }  
使用submit 方法来提交任务,它会返回一个Future对象,通过future的get方法来获取返回值,get方法会阻塞住直到任务完成,而使用get(long timeout, TimeUnit unit)方法则会阻塞一段时间后立即返回,这时有可能任务没有执行完。

线程池工作流程分析:(来自参考文章)

从上图我们可以看出,当提交一个新任务到线程池时,线程池的处理流程如下:
1、首先线程池判断基本线程池是否已满(< corePoolSize ?)?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
2、其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
3、最后线程池判断整个线程池是否已满(< maximumPoolSize ?)?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。

也就是说,线程池优先要创建出基本线程池大小(corePoolSize)的线程数量,没有达到这个数量时,每次提交新任务都会直接创建一个新线程,当达到了基本线程数量后,又有新任务到达,优先放入等待队列,如果队列满了,才去创建新的线程(不能超过线程池的最大数maxmumPoolSize)。

关于线程池的配置原则可阅读参考文章。

ThreadPoolExecutor简单实例:
[java]  view plain  copy
  1. public class BankCount {  
  2.     public synchronized void addMoney(int money){//存钱  
  3.         System.out.println(Thread.currentThread().getName() + ">存入:" + money);  
  4.     }  
  5.   
  6.     public synchronized void getMoney(int money){//取钱  
  7.         System.out.println(Thread.currentThread().getName() + ">取钱:" + money);  
  8.     }  
  9. }  
测试类:
[java]  view plain  copy
  1. public class BankTest {  
  2.     public static void main(String[] args) {  
  3.         final BankCount bankCount = new BankCount();  
  4.   
  5.         ExecutorService executor = Executors.newFixedThreadPool(10);  
  6.         executor.execute(new Runnable() {//存钱线程  
  7.             @Override  
  8.             public void run() {  
  9.                 int i = 5;  
  10.                 while(i-- > 0){  
  11.                     bankCount.addMoney(200);  
  12.                     try {  
  13.                         Thread.sleep(500);  
  14.                     } catch (InterruptedException e) {  
  15.                         e.printStackTrace();  
  16.                     }  
  17.                 }  
  18.             }  
  19.         });  
  20.         Future<?> future = executor.submit(new Runnable() {//取钱线程  
  21.             @Override  
  22.             public void run() {  
  23.                 int i = 5;  
  24.                 while(i-- > 0){  
  25.                     try {  
  26.                         Thread.sleep(500);  
  27.                     } catch (InterruptedException e) {  
  28.                         e.printStackTrace();  
  29.                     }  
  30.                     bankCount.getMoney(200);  
  31.                 }  
  32.             }  
  33.         });  
  34.         try {  
  35.             Object res = future.get();  
  36.             System.out.println(res);  
  37.         } catch (InterruptedException e) {  
  38.             // 处理中断异常  
  39.             e.printStackTrace();  
  40.         } catch (ExecutionException e) {  
  41.             // 处理无法执行任务异常  
  42.             e.printStackTrace();  
  43.         }finally{  
  44.             // 关闭线程池  
  45.             executor.shutdown();  
  46.         }  
  47.     }  
  48. }  
打印结果如下:
pool-1-thread-1>存入:200
pool-1-thread-1>存入:200
pool-1-thread-2>取钱:200
pool-1-thread-1>存入:200
pool-1-thread-2>取钱:200
pool-1-thread-1>存入:200
pool-1-thread-2>取钱:200
pool-1-thread-1>存入:200
pool-1-thread-2>取钱:200
pool-1-thread-2>取钱:200
null

可以看到,打印出来的future.get()获取的结果为null,这是因为Runnable是没有返回值的,需要返回值要使用Callable,这里就不再细说了,具体可参考如下文章:

2、生产者和消费者模型

生产者消费者模型,描述是:有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者可以从仓库中取走产品。解决消费者和生产者问题的 核心在于保证同一资源被多个线程并发访问时的完整性 。一般采用信号量或加锁机制解决。下面介绍Java中解决生产者和消费者问题主要三种仿:
(1)wait() / notify()、notifyAll()
wait和notify方法是Object的两个方法,因此每个类都会拥有这两个方法。
wait()方法:使当前线程处于等待状态,放弃锁,让其他线程执行。
notify()方法:唤醒其他等待同一个锁的线程,放弃锁,自己处于等待状态。
如下例子:
[java]  view plain  copy
  1. /** 
  2.  * 仓库 
  3.  */  
  4. public class Storage {  
  5.     private static final int MAX_SIZE = 100;//仓库的最大容量  
  6.     private List<Object> data = new ArrayList<Object>();//存储载体  
  7.     /** 
  8.      * 生产操作 
  9.      */  
  10.     public synchronized void produce(int num){  
  11.         if(data.size() + num > MAX_SIZE){//如果生产这些产品将超出仓库的最大容量,则生产操作阻塞  
  12.             System.out.println("生产操作-->数量:" + num + ",超出仓库容量,生产阻塞!------库存:" + data.size());  
  13.             try {  
  14.                 wait();  
  15.             } catch (InterruptedException e) {  
  16.                 e.printStackTrace();  
  17.             }  
  18.         }  
  19.         //到这里,表示可以正常生产产品  
  20.         for(int i = 0; i < num; i++){//生产num个产品  
  21.             data.add(new Object());  
  22.         }  
  23.         System.out.println("生产操作-->数量:" + num + ",成功入库~------库存:" + data.size());  
  24.         //生产完产品后,唤醒其他等待消费的线程  
  25.         notify();  
  26.     }  
  27.   
  28.     /** 
  29.      * 消费操作 
  30.      */  
  31.     public synchronized void consume(int num){  
  32.         if(data.size() - num < 0){//如果产品数量不足  
  33.             System.out.println("消费操作-->数量:" + num + ",库存不足,消费阻塞!------库存:" + data.size());  
  34.             try {  
  35.                 wait();  
  36.             } catch (InterruptedException e) {  
  37.                 e.printStackTrace();  
  38.             }  
  39.         }  
  40.         //到这里,表示可以正常消费  
  41.         for(int i = 0; i < num; i++){//消费num个产品  
  42.             data.remove(0);  
  43.         }  
  44.         System.out.println("消费操作-->数量:" + num + ",消费成功~------库存:" + data.size());  
  45.         //消费完产品后,唤醒其他等待生产的线程  
  46.         notify();  
  47.     }  
  48.   
  49. }  
生产者:
[java]  view plain  copy
  1. public class Producer implements Runnable{  
  2.     private Storage storage;  
  3.     private int num;//每次生产多少个  
  4.   
  5.     public Producer(Storage sto,int num){  
  6.         storage = sto;  
  7.         this.num = num;  
  8.     }  
  9.   
  10.     @Override  
  11.     public void run() {  
  12.         storage.produce(num);  
  13.     }  
  14. }  
消费者:
[java]  view plain  copy
  1. public class Consumer implements Runnable{  
  2.     private Storage storage;  
  3.     private int num;//每次消费多少个  
  4.   
  5.     public Consumer(Storage sto,int num){  
  6.         storage = sto;  
  7.         this.num = num;  
  8.     }  
  9.   
  10.     @Override  
  11.     public void run() {  
  12.         storage.consume(num);  
  13.     }  
  14. }  
测试类:
[java]  view plain  copy
  1. public class StorageTest {  
  2.     public static void main(String[] args) {  
  3.         Storage storage = new Storage();  
  4.         ExecutorService taskSubmit = Executors.newFixedThreadPool(10);    //来使用使用上一节我们总结的线程池知识  
  5.   
  6.         //给定4个消费者  
  7.         taskSubmit.submit(new Consumer(storage, 30));  
  8.         taskSubmit.submit(new Consumer(storage, 10));  
  9.         taskSubmit.submit(new Consumer(storage, 20));  
  10.   
  11.         //给定6个生产者  
  12.         taskSubmit.submit(new Producer(storage, 70));  
  13.         taskSubmit.submit(new Producer(storage, 10));  
  14.         taskSubmit.submit(new Producer(storage, 20));  
  15.         taskSubmit.submit(new Producer(storage, 10));  
  16.         taskSubmit.submit(new Producer(storage, 10));  
  17.         taskSubmit.submit(new Producer(storage, 10));  
  18.   
  19.         taskSubmit.shutdown();  
  20.     }  
  21. }  
打印结果:
消费操作-->数量:30,库存不足,消费阻塞!------库存:0
生产操作-->数量:10,成功入库~------库存:10
生产操作-->数量:70,成功入库~------库存:80
生产操作-->数量:10,成功入库~------库存:90
生产操作-->数量:10,成功入库~------库存:100
生产操作-->数量:20,超出仓库容量,生产阻塞!------库存:100
消费操作-->数量:10,消费成功~------库存:90
生产操作-->数量:20,成功入库~------库存:110
生产操作-->数量:10,超出仓库容量,生产阻塞!------库存:110
消费操作-->数量:20,消费成功~------库存:90
消费操作-->数量:30,消费成功~------库存:60
生产操作-->数量:10,成功入库~------库存:70

在仓库中,唤醒我们使用的是notify()而没有使用notifyAll(),是因为在这里,如果测试数据设置不当很容易造成死锁(比如一下唤醒了所有的生产进程),因为使用wait和notify有一个缺陷:
逻辑本应该要这样设计的,在produce()操作后,只要唤醒等待同一把锁的消费者进程,在consume()后,唤醒等待同一把锁的生产者进程,而notify()或notifyAll()将生产者和消费者线程都唤醒了。下面的第二种方法可以解决这个问题。

wait和notify在“类消费者和生产者”问题上也很有用,比如,在A类的某个方法中调用了传进来的B对象的一个方法,A类方法的后面代码依赖于刚刚调用的B的返回值,但是B对象的这个方法是一个异步的操作,此时就可以在A方法中调用完B对象的方法后自我阻塞,即调用wait()方法,而在B对象的那个方法中,待异步操作完成后,调用notify(),唤醒处于等待同一锁对象的线程。如下:
A类的某个方法中:     
[java]  view plain  copy
  1. XmppManager xmppManager = notificationService.getXmppManager();  
  2.                 if(xmppManager != null){  
  3.                     if(!xmppManager.isAuthenticated()){  
  4.                         try {  
  5.                                 synchronized (xmppManager) {//等待客户端连接认证成功  
  6.                                 Log.d(LOGTAG, "wait for authenticated...");  
  7.                                 xmppManager.wait();  
  8.                             }  
  9.                         } catch (InterruptedException e) {  
  10.                             e.printStackTrace();  
  11.                         }  
  12.                     }  
    //运行到此处,说明是认证成功的,有两种可能,一是运行速度很快调用notificationService.getXmppManager()后直接返回了结果,二是B中处理完了调用notify方法
    Log.d(LOGTAG, "authenticated already. send SetTagsIQ now...");
B中处理完后:
    //客户端连接认证成功后,唤醒拥有xmppManager锁的对象
      synchronized (xmppManager) {
           xmppManager.notifyAll();
     }

(2)await() / signal()
在JDK1.5之引入concurrent包之后,新引入了await()和signal()方法来做同步,功能和wait()和notify()方法相同,可以完全取代,但await()和signal()需要和Lock机制(关于Lock机制前面已总结)结合使用,更加灵活。正如第一种所说,可以通过调用Lock的newCondition()方法依次获取两个条件变量,一个针对仓库空的,一个针对仓库满的条件变量,通过添加变量进行同步控制。
修改仓库类Storage:
[java]  view plain  copy
  1. /** 
  2.  * 仓库 
  3.  */  
  4. public class Storage {  
  5.     private static final int MAX_SIZE = 100;//仓库的最大容量  
  6.     private List<Object> data = new ArrayList<Object>();//存储载体  
  7.   
  8.     private Lock lock = new ReentrantLock();//可重入锁  
  9.     private Condition full = lock.newCondition();//仓库满的条件变量  
  10.     private Condition empty = lock.newCondition();//仓库空时的条件变量  
  11.   
  12.     /** 
  13.      * 生产操作 
  14.      */  
  15.     public void produce(int num){  
  16.         lock.lock();    //加锁  
  17.         if(data.size() + num > MAX_SIZE){//如果生产这些产品将超出仓库的最大容量,则生产操作阻塞  
  18.             System.out.println("生产操作-->数量:" + num + ",超出仓库容量,生产阻塞!------库存:" + data.size());  
  19.             try {  
  20.                 full.await();    //阻塞  
  21.             } catch (InterruptedException e) {  
  22.                 e.printStackTrace();  
  23.             }  
  24.         }  
  25.         //到这里,表示可以正常生产产品  
  26.         for(int i = 0; i < num; i++){//生产num个产品  
  27.             data.add(new Object());  
  28.         }  
  29.         System.out.println("生产操作-->数量:" + num + ",成功入库~------库存:" + data.size());  
  30.         //生产完产品后,唤醒其他等待消费的线程  
  31.         empty.signalAll();  
  32.   
  33.         lock.unlock();    //释放锁  
  34.     }  
  35.   
  36.     /** 
  37.      * 消费操作 
  38.      */  
  39.     public void consume(int num){  
  40.         lock.lock();    //加锁  
  41.         if(data.size() - num < 0){//如果产品数量不足  
  42.             System.out.println("消费操作-->数量:" + num + ",库存不足,消费阻塞!------库存:" + data.size());  
  43.             try {  
  44.                 empty.await();    //阻塞  
  45.             } catch (InterruptedException e) {  
  46.                 e.printStackTrace();  
  47.             }  
  48.         }  
  49.         //到这里,表示可以正常消费  
  50.         for(int i = 0; i < num; i++){//消费num个产品  
  51.             data.remove(0);  
  52.         }  
  53.         System.out.println("消费操作-->数量:" + num + ",消费成功~------库存:" + data.size());  
  54.         //消费完产品后,唤醒其他等待生产的线程  
  55.         full.signalAll();  
  56.   
  57.         lock.unlock();    //释放锁  
  58.     }  
  59. }  
打印结果:
消费操作-->数量:30,库存不足,消费阻塞!------库存:0
消费操作-->数量:10,库存不足,消费阻塞!------库存:0
消费操作-->数量:20,库存不足,消费阻塞!------库存:0
生产操作-->数量:70,成功入库~------库存:70
生产操作-->数量:10,成功入库~------库存:80
生产操作-->数量:10,成功入库~------库存:90
生产操作-->数量:10,成功入库~------库存:100
生产操作-->数量:10,超出仓库容量,生产阻塞!------库存:100
消费操作-->数量:30,消费成功~------库存:70
消费操作-->数量:10,消费成功~------库存:60
消费操作-->数量:20,消费成功~------库存:40
生产操作-->数量:10,成功入库~------库存:50
生产操作-->数量:20,成功入库~------库存:70

使用await和signal后,加锁解锁操作就交给了Lock,不用再使用synchronized同步(具体可看前面总结的同步的实现方法),在produce中满仓后阻塞,生产完后唤醒等待的消费线程,consume中库存不足后阻塞,消费完后唤醒等待的生产者线程,表示可以消费了。

(3)BlockingQueue阻塞队列方式
在上一节关于线程池的总结中,我们看到了要创建一个线程池如ThreadPoolExecutor,需要传入一个任务队列即BlockingQueue,BlockingQueue(接口)用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue。
    >ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
    >LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
    >SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
    >PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

BlockingQueue的所有实现类内部都是已经实现了同步的队列,实现的方式采用的是上面介绍的第二种await()/signal() + Lock同步的机制。在生成阻塞队列时,可以指定队列大小。用于阻塞操作的方法主要为:
    put()方法:插入一个元素,如果超过容量则自我阻塞,等待唤醒;
    take()方法:取走一个元素,如果容量不足了,自我阻塞,等待唤醒;

put和take内部自己实现了await和signal、lock的机制处理,不再需要我们做相应操作。修改Storage代码如下:
[java]  view plain  copy
  1. public class Storage {  
  2.     private static final int MAX_SIZE = 100;//仓库的最大容量  
  3.     private BlockingQueue<Object> data = new LinkedBlockingQueue<Object>(MAX_SIZE);    //使用阻塞队列作为存储载体  
  4.     /** 
  5.      * 生产操作 
  6.      */  
  7.     public void produce(int num){  
  8.         if(data.size() == MAX_SIZE){//如果仓库已达最大容量  
  9.             System.out.println("生产操作-->仓库已达最大容量!");  
  10.         }  
  11.         //到这里,表示可以正常生产产品  
  12.         for(int i = 0; i < num; i++){//生产num个产品  
  13.             try {  
  14.                 data.put(new Object());    //put内部自动实现了判断,超过最大容量自动阻塞  
  15.             } catch (InterruptedException e) {  
  16.                 e.printStackTrace();  
  17.             }  
  18.         }  
  19.         System.out.println("生产操作-->数量:" + num + ",成功入库~------库存:" + data.size());  
  20.     }  
  21.   
  22.     /** 
  23.      * 消费操作 
  24.      */  
  25.     public void consume(int num){  
  26.         if(data.size() ==  0){//如果产品数量不足  
  27.             System.out.println("消费操作--库存不足!");  
  28.         }  
  29.         //到这里,表示可以正常消费  
  30.         for(int i = 0; i < num; i++){//消费num个产品  
  31.             try {  
  32.                 data.take();    //take内部自动判断,消耗后库存是否充足,不足自我阻塞  
  33.             } catch (InterruptedException e) {  
  34.                 e.printStackTrace();  
  35.             }  
  36.         }  
  37.         System.out.println("消费操作-->数量:" + num + ",消费成功~------库存:" + data.size());  
  38.     }  
  39. }  
打印结果:
消费操作--库存不足!
消费操作--库存不足!
消费操作--库存不足!
生产操作-->数量:70,成功入库~------库存:45
消费操作-->数量:30,消费成功~------库存:45
生产操作-->数量:10,成功入库~------库存:56
生产操作-->数量:20,成功入库~------库存:75
生产操作-->数量:10,成功入库~------库存:85
生产操作-->数量:10,成功入库~------库存:89
消费操作-->数量:10,消费成功~------库存:60
生产操作-->数量:10,成功入库~------库存:70
消费操作-->数量:20,消费成功~------库存:70

可以看到,Storage中produce和consume方法中我们直接通过put和take方法往容器中添加或移除产品即可,没有进行逻辑控制(其实上面两个方法中if都可以去掉,只是为了打印效果才加上的),这是因为BlockingQueue内部已经实现了,不需要我们再次控制。
同时,我们看到打印的库存信息出现了不匹配,这个主要是因为我们的打印语句Systm.out.println()没有被同步导致的,因为同步语句只是在put和take方法内部,而我们打印语句中使用了data这个共享变量。这里因为我们需要看效果,所以才加的打印语句,并不影响我们对BlockingQueue的使用。

因此,在Java中,使用BlockingQueue阻塞队列的方式可以很方便的为我们处理生产者消费则问题,推荐使用。

在我们的编程生涯中,我们自己要去写生产者和消费者问题,多是前面第一种介绍的“类似消费者生产者问题”上。

解决生产者和消费者问题还有管道的方式,即在生产者和消费者之间建立一个管道缓冲区,Java中用PipedInputStream / PipedOutputStream实现,由于这种方式对于传输对象不易封装,因此实用性不高,就不具体介绍了。

3、sleep和wait的区别

sleep是Thread的静态方法,wait是Object的方法。两个方法都会暂停当前线程
(1)sleep使当前线程阻塞,让出CPU,给其他线程执行的机会;如果当前线程拥有锁,不会释放锁,也即“睡着我也要拥有锁”。睡眠时间一到,进入就绪状态,如果当前CPU空闲,才会继续执行。
(2)wait方法调用后,当前线程进入阻塞状态,进入到和该对象(即谁调用了wait()方法,如list.wait())相关的等待池中。,让出CPU,给其他线程执行的机会;当超时间过了或者别的线程调用了notify()或notifyAll()方法时才会唤醒当前等待同一把锁的线程。
(3)wait方法必须要放在同步块中,如syncbronized或Lock同步中。

所以sleep和wait的主要区别是:
sleep:保持锁,睡眠时间到进入就绪状态;
wait:释放锁,等待其他线程的notify操作或超时唤醒。

参考文章:
MT6771 Android13 背光曲线调试与光感数据校准实战 本文详细介绍了MT6771平台在Android 13系统上进行背光曲线调试与光感数据校准的实战经验。针对高亮度环境下背光误调节问题,深入分析了光感数据上报机制,并提供了驱动层配置修改、背光曲线优化及常见问题解决方案,帮助开发者快速定位并解决类似问题。 阅读详情

相关推荐

程序员实战:DINOv3 权重文件下载与本地化部署全流程指南

Meta开源的DINOv3视觉大模型凭借70亿参数和17亿图像训练,在语义分割、目标检测等任务表现优异。本文详解预训练权重的获取渠道、校验方法和加载实践。开发者可通过HuggingFace或GitHub获取不同版本权重,包括针对服务器端的ViT系列和边缘设备的ConvNeXt系列。文章提供MD5校验代码确保文件完整性,并给出PyTorch和Transformers两种加载方式。特别强调7B模型需16GB GPU内存,建议根据硬件条件选择合适版本,如边缘设备使用ConvNeXt-Tiny。最后指出DINOv3

xiugou3365的博客 5911

JAVA线程池详解

最全java线程池详解,看这一篇就够了...

ZHANGLIZENG的博客 1万+

开源TMS系统cortex-tms私有化部署与二次开发实战指南

运输管理系统(TMS)是现代物流数字化的核心组件,它通过将线下零散的物流操作转化为线上标准化、可追溯的数据流,实现订单、调度、跟踪与结算的全流程管理。其技术原理通常基于微服务架构,采用Spring Boot、Vue.js等成熟技术栈构建,确保系统的稳定性和可扩展性。TMS的技术价值在于显著提升物流企业的运营效率、降低成本并增强过程可控性,尤其适用于中小型物流企业实现从手工管理到系统化管理的跨越。在应用场景上,TMS广泛应用于车队管理、订单跟踪和财务结算等环节。本文聚焦于开源TMS项目cortex-tms,深

weixin_42523907的博客 747

Java笔记之线程池详解

这里是针对JDK1.8版本,使用JDK自带的线程池会出现OOM问题,中小型公司一般很难遇到,在阿里巴巴开发文档上面有明确的标识:上边我们已经分析了线程池的几个参数,这几个参数核心线程数、最大线程数、活跃时间和单位根据服务器本身的性能和程序的特性设定。但是线程工厂、决绝策略、阻塞队列又该怎么搞呢?拒绝策略其实很简单,ExecutorService构造时可以不传递拒绝策略,默认使用异常抛出的方式。阻塞队列我们搞一个定长的队列就好了,ArrayBlockingQueue(DEFAULT_SIZE)

LP'S Blog 1679

pi-star图形界面安装指南

pi-star图形界面安装指南,MMDVM热点安装,树莓派3B+热点

blueisman的专栏 1911

Zipformer实战:5分钟快速搭建语音识别模型(附LibriSpeech测试结果)

本文提供了一份详细的Zipformer语音识别模型实战指南。通过清晰的步骤,指导读者在5分钟内完成从环境配置、LibriSpeech数据集准备到模型训练与测试的全过程。文章重点解析了关键配置参数,并分享了在LibriSpeech测试集上的性能结果,帮助开发者快速评估和应用这一高效的语音识别架构。

weixin_35578748的博客 164

线程池知识点

自定义线程池就需要我们自己配置最大线程数maximumPoolSize,为了高效的并发运行,当。CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几。I0密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行I0密集型的任务会导。因为频繁的开启线程或者停止线程,线程需要从新被cpu从就绪到运行状态调度,需要发生。线程池和数据库连接池非常类似,可以统一管理和维护线程,减少没有必要的开销。

小牛童鞋 278

GPU运维工程师入门指南 (第九篇): 高性能网络 - InfiniBand与RDMA技术

本文介绍了支撑AI分布式训练的高性能网络技术InfiniBand和RDMA。传统TCP/IP网络因内核开销和内存拷贝成为性能瓶颈,而InfiniBand提供高带宽、低延迟的专用网络,RDMA则通过内核旁路和零拷贝技术实现节点间内存直接访问,显著提升通信效率。特别介绍了GPUDirect RDMA技术,使GPU显存可直接通信。文章还列举了运维人员在部署这些技术时需要考虑的硬件、驱动、配置和监控等关键点。这些技术共同构成了支撑大规模AI集群的"数据高速公路",为后续讨论存储性能瓶颈奠定了基础

weixin_42587823的博客 1100

线程池基础知识整理

最近换了工作,选择了人生中的第二家公司,刚来这家公司感觉这家公司的流量可能比我上一家公司的网站流量要大,所以在入职第一周开始学习并发编程的相关技术。因为完全是自己自学的思路,所以学习的路径可能比较混乱。 首先我觉得需要对多线程有个宏观的概念。 下图是摘抄自网上,其中黄色的代表接口,蓝色的代表类。

从前不爱吃面的大叔 435

STM32H743实战(三)-- 时钟系统配置

本文详细讲解了STM32H743时钟系统的配置方法,重点解析了为何选择400MHz而非理论极限480MHz作为稳定运行的主频。通过STM32CubeIDE环境,手把手指导如何从外部晶振配置、电源电压设置到PLL锁相环参数调整,最终实现稳定可靠的400MHz系统时钟,并提供了代码验证与常见问题排查指南。

weixin_29164185的博客 368

AJ-Report开源数据大屏存在远程命令执行漏洞

该平台可以通过post方式在validationRules参数对应值中进行命令执行,可以获得服务器权限,登陆管理后台接管大屏。如果被攻击者利用,危害后果十分严重。

swordheart的博客 388

【JavaEE】线程池和定时器

在深入研究Java并发编程的过程中,线程池和定时器是两个不可或缺的概念。它们不仅体现了Java在处理并发和定时任务上的强大功能,也反映了现代软件工程中资源管理和效率优化的重要性。以下是我在学习这两个主题后的总结与反思。线程池:高效资源管理的艺术线程池,顾名思义,是一个预先创建好的线程集合,用于执行提交的任务。它通过复用现有线程,避免了线程创建和销毁的开销,从而提高了系统的响应速度和资源利用率。线程池的配置参数,如核心线程数、最大线程数、工作队列类型等,需要根据具体的应用场景和系统资源进行细致调整。

2302_79806056的博客 1822

记录:MCGS和正弦变频器通讯

触摸屏实现对变频器:启动、停止、频率设定、频率读取、电压读取、电流读取。通讯方式Modbus RTU。

m0_61837027的博客 1026

【多线程】定时器和线程池

定时器是软件开发中一个重要的主键,类似于一个“闹钟”,达到一个设定的时间之后, 就执行某个指定 好的代码。比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连。比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除). 类似于这样的场景就需要用到定时器。标准库中提供了一个Timer 类。Timer 类中的核心方法为 schedule。......

m0_60494863的博客 1150

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

定时器 和 线程池

认为内核态效率低,不一定是真的低,而是随着代码进入内核态,就不可控了.内核可能还要做其他的事情 内核什么时候把活干完,把结果给你 (有时候快,有时候慢)

m0_58154870的博客 508

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

五、线程池和定时器

五、线程池和定时器

shiyu_951的博客 705

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

Java EE初阶——定时器和线程池

工厂方法让类的实例化推迟到子类中进行。

2302_80250536的博客 1177

6.定时器和线程池

一、定时器 定时器可以间隔特定的时间,用来执行特定的程序 定时器执行任务时,不会占用我们当前的执行流 1. Java.util包下的Timer类---“闹钟” 1.1 schedule(TimeTask task,long delay)方法 等到delay毫秒后执行一次任务 import java.util.Timer; import java.util.TimerTask; /** * @author happy */ public class UseT..

qq_46235384的博客 746

【JavaEE】阻塞队列、定时器和线程池

1、阻塞队列 1.1、概念 1.2、生产者消费者模型 1.3、阻塞队列的模拟实现 2、定时器 2.1、使用标准库中的定时器 2.2、模拟实现定时器 3、线程池 3.1、标准库中的线程池 3.1.1、ThreadPoolExecutor类的构造方法 3.1.2、Java标准库的4种拒绝策略【经典面试题】【重点】 3.1.3、工厂模式 3.2、模拟实现线程池

m0_73067372的博客 779

JavaEE初阶——多线程(6)定时器和线程池

本文介绍了定时器和线程池的实现原理及应用。定时器部分详细讲解了标准库Timer类的使用,并实现了自定义定时器功能,包括任务队列管理、防止忙等优化以及多线程场景下的处理方案。线程池部分阐述了线程池的工作机制,分析了核心线程、临时线程及拒绝策略等关键参数,比较了AbortPolicy、CallerRunsPolicy等四种拒绝策略的特点。最后提供了一个简单的线程池实现示例,展示了任务队列管理和线程调度的基本逻辑。这两种技术都能有效管理系统资源,提高程序执行效率。

Yoko_999的博客 1190

线程Queue、定时器、进程池和线程池、同步异步

目录 线程Queue、定时器、进程池和线程池、多线程socket通信 一、Queue队列实现线程通信 二、线程定时器(Timer) 三、进程池和线程池 四、同步和异步 4.1、同步 4.2 、异步 五、多线...

aq212000的博客 327

JavaSE多线程之线程池和定时器

JavaSE多线程之线程池和计时器 1.线程间的等待唤醒机制 Object 类中 void wait () 在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待。 void wait (long timeout) 在其他线程调用此对象的 notify () 方法或 notifyAll () 方法,或者超过指定的时间量前,导致当前线程等待。 void notify () 唤醒在此对象监视器上等待的单个线程。 void notifyAll () 唤醒在此

ITzhazhassc的博客 235

定时器和线程池

定时器和线程池

qq_45283185的博客 377

定时器、线程queue、进程池和线程池

1.定时器 指定n秒后,执行任务 1 from threading import Timer,current_thread 2 import os 3 4 5 def hello(): 6 print("%s hello, world"%os.getpid()) 7 print("%s hello, world"%current_...

ZHOUXIN0426的博客 223

定时器的简单实现和线程池基础知识(1)

定时器 && 线程池

weixin_49301912的博客 201
上一篇: tomcat负载均衡问题
下一篇: phantomJs+CasperJS windows上安装
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值