关于一些基础的Java问题的解答(六)

上一篇文章的传送门:关于一些基础的Java问题的解答(五)


26. ThreadPool用法与优势

ThreadPool即线程池,它是JDK1.5引入的Concurrent包中用于处理并发编程的工具。使用线程池有如下好处:
  1. 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
  2. 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
线程池的创建方法如下:
通过ThreadPoolExecutor创建:
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
该构造函数具有重载,最后两个参数史可选的,各参数的含义如下:
  1. corePoolSize(基本线程池大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于基本线程池大小时就不再创建。线程池有一个prestartAllCoreThreads方法用来提前创建并启动所有基本线程
  2. maximumPoolSize(最大线程池大小):线程池允许创建的最大线程数,必须大于基本线程池大小。线程池中含有一个任务阻塞队列,如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务
  3. keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率
  4. unit(线程活动保持时间的单位):前一个参数的时间单位
  5. workQueue(工作队列):用于保存等待执行的任务的阻塞队列,如果当任务提交时基本线程池中已没有空闲线程,则任务会被放入工作队列等待
  6. threadFactory(线程工厂,可选参数):创建一个新线程时会调用其newThread方法,可以初始化一些信息
  7. handler(饱和处理器):当队列和线程池都满了,说明线程池处于饱和状态,那么就会使用处理器处理提交的新任务。ThreadPoolExecutor内部提供了一些handler的静态实现类:
  • AbortPolicy:直接抛出异常RejectedExecutionException
  • CallerRunsPolicy:使用调用者所在线程来运行任务
  • DiscardOldestPolicy:丢弃队列头的任务,并执行当前任务
  • DiscardPolicy:什么也不做,放弃掉该任务
除了自定义线程池,我们还可以使用Executors类中提供的实现好的线程池:
// 为每个任务创建一个新线程或回收利用旧线程的线程池
		ExecutorService service1 = Executors.newCachedThreadPool();
		// 创建指定数目线程的线程池
		ExecutorService service2 = Executors.newFixedThreadPool(nThreads);
		// 线程数量固定为1的线程池,拥有无界的工作队列
		ExecutorService service3 = Executors.newSingleThreadExecutor();
以上三种线程池为比较常用的线程池,其余的不再探讨。
线程池的用法如下:
		// 传入一个Runnable接口处理任务
		executor.execute(runnable);
		// 传入一个Callable接口处理任务,返回Future对象代表任务返回值
		Future<Object> result = executor.submit(callable);
		// 使用get方法获取具体内容
		result.get();
		// 关闭线程池,中断空闲线程
		executor.shutdown();
		// 关闭线程池,中断所有线程
		executor.shutdownNow();

综上,线程池的工作流程为:
关于一些基础的Java问题的解答(六)_第1张图片

27. Concurrent包里的工具:ArrayBlockingQueue、CountDownLatch等等

JDK1.2引入的容器类库为了效率问题所以是不同步的,要同步容器类我们只能自己实现或指望Collections类提供的各种static同步方法。但在JDK1.5中引入的Concurrent包中,Java为我们提供了许多线程安全的免锁容器,主要使用的有以下几种:
  1. CopyOnWriteArrayList:写时拷贝列表,对容器的写入操作将导致创建整个底层数组的副本,而原数组保存在原地,使得当数组在被修改时,读取可以安全的执行。修改完成时,一个原子性的操作会把新数组换入,使得新的读取操作可以看到修改
  2. CopyOnWriteArraySet:与CopyOnWriteArrayList类似的集合
  3. ConcurrentHashMap:详见问题11
  4. ConcurrentLinkedQueue:一个线程安全的队列
  5. DelayQueue:延迟队列,是一个无界阻塞队列,对于放入的元素实现延迟接口,设定延迟时间,元素只有过了延迟时间后才能被取走
  6. LinkedBlockingQueue:阻塞队列,LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来
  7. PriorityBlockingQueue:优先级阻塞队列,与优先级队列相似,具有阻塞的特点

除了容器外,Concurrent还为我们提供了各种用于同步的辅助类,常见的有以下几种:
  1. Atomic类,原子类,各种包装类型的同步类,可以使用compareAndSet形式的方法来更新变量
  2. CountDownLatch:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。其构造方法可以指定计数次数,当其countDown方法被调用时次数减一,而调用awaint方法(可以设置超时)会使当前线程一直被阻塞,直到计时器的值为0
  3. CyclicBarrier:回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。其构造方法可以指定线程数,还可以传入Runnable表示所有线程到达状态后要执行的内容。线程调用其await方法时表示线程已到达指定状态并被阻塞,当有所有线程都到达状态时,线程才可以继续往下执行。调用其reset方法可以重复使用。
  4. Exchanger:用于两个线程交换数据的辅助类,调用exchange方法后线程会被阻塞知道另一个线程调用exchange方法(可以设置超时)
  5. ReadWriteLock:读写锁允许我们拥有多个读者,对向数据结构相对不频繁的写入但频繁读取做了优化。我们分别可以调用readLock和writeLock方法获取读锁和写锁,使用lock和unlock方法来加锁和解锁。
  6. Semaphore:信号量,可以控同时访问的线程个数(通过构造函数设置),通过 acquire方法 获取一个许可,如果没有就等待,而 release方法 释放一个许可。

28. wait()和sleep()的区别

wait是Object类的方法,只有当线程拥有调用对象的锁的时候才可以调用该方法,否则会抛出IllegalMonitorStateException。wait方法的作用是阻塞当前线程并让出调用对象的锁,直到别的线程使用notify或notifyAll唤醒,或经过特定时间,或被别的线程中断才继续工作。
sleep是Thread类的静态方法,他可以让当前线程休眠一段时间,直到经过特定休眠时间,或被别的线程中的才继续工作。

29. foreach与正常for循环效率对比

for的写法都比较熟悉就不提了。
foreach语句是JDK1.5的新特征之一,在遍历数组、集合方面,foreach为开发人员提供了极大的方便。foreach语句是for语句的特殊简化版本,但是foreach语句并不能完全取代for语句,然而,任何的foreach语句都可以改写为for语句版本。foreach并不是一个关键字,习惯上将这种特殊的for语句格式称之为“foreach”语句。可以使用foreach的对象必须实现Iterator 接口。
一般而言,只是遍历的话我们可以使用foreach,如果要涉及对数组或容器的操作就只能使用for循环了。

30. Java IO与NIO

Java NIO(Java new IO),是jdk1.4 里提供的新api ,为所有的原始类型提供缓存支持。
Java IO是面向流的而Java NIO是面向缓冲的,由于博主对Java NIO了解不多,故此问题先搁置在此。

你可能感兴趣的:(java,基础,常见问题)