JDK并发包里常用的类

简介: 笔记

文章参考


  • ConcurrentHashMap
    是线程安全的。
    Put方法,首先是对key.hashCode进行hash操作,得到hash值。然后获取对应的segment对象,接着调用Segment对象的put方法完成当前操作。当调用put方法时,首先lock操作,完成操作后再释放锁。
    http://ifeve.com/concurrenthashmap/
  • Semaphore
    可以控制某资源同时被访问的个数。例如连接池中通常要控制创建连接的个数。
    tryAcquire方法,获得锁  release方法,释放锁
  • CountdownLatch
    闭锁,确保一个服务不会开始,直到它依赖的其他服务都已近开始,它允许一个或多个线程,等待一个事件集的发生。  通过减计数的方式,控制多个线程同时开始某个动作。当计数为0时,await后的代码才会被执行。 提供await()和countDown()两个方法。
  • CyclicBarrier
    cyclicBarrier中的await方法会对count值减1,并阻塞当前线程(java.util.concurrent.locks.Condition.await()),如果count==0时先执行CyclicBarrier内部的Runnable任务(java.lang.Runnable.run()),然后唤醒所有阻塞的线程(java.util.concurrent.locks.Condition.signalAll()),count恢复初始值(可以进入下一轮循环)。
    与CountdownLatch不同的是,它可以循环重用。
import java.util.concurrent.CyclicBarrier;
public class TestCyclicBarrier {
    private static final int THREAD_NUM = 5;
    public static class WorkerThread implements Runnable {
        CyclicBarrier barrier;
        public WorkerThread(CyclicBarrier b){
            this.barrier = b;
        }
        @Override
        public void run() {
            try {
                System.out.println("Worker's waiting");
                // 线程在这里等待,直到所有线程都到达barrier。
                barrier.await();
                System.out.println("ID:" + Thread.currentThread().getId() + " Working");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        CyclicBarrier cb = new CyclicBarrier(THREAD_NUM, new Runnable() {
            // 当所有线程到达barrier时执行
            @Override
            public void run() {
                System.out.println("Inside Barrier");
            }
        });
        for (int i = 0; i < 10; i++) {
            new Thread(new WorkerThread(cb)).start();
        }
    }
}

结果:

Worker's waiting
Worker's waiting
Worker's waiting
Worker's waiting
Worker's waiting
Inside Barrier
ID:13 Working
ID:9 Working
ID:12 Working
ID:11 Working
ID:10 Working
Worker's waiting
Worker's waiting
Worker's waiting
Worker's waiting
Worker's waiting
Inside Barrier
ID:18 Working
ID:14 Working
ID:16 Working
ID:15 Working
ID:17 Working
  • AtomicInteger
    原子操作,线程安全。之前如果多线程累计计数,需要通过锁控制。 IncrementAndGet方法,关键是调用了compareAndSwap方法,是native方法,基于cpu的CAS原语来实现的。简单原理是由cpu比较内存位置上的值是否为当前值,如果是换成新值,否则返回false
  • ThreadPoolExecutor
    提供线程池服务,ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler)
corePoolSize: 线程池维护线程的最少数量
maximumPoolSize:线程池维护线程的最大数量
keepAliveTime: 线程池维护线程所允许的空闲时间
unit: 线程池维护线程所允许的空闲时间的单位
workQueue: 线程池所使用的缓冲队列
handler: 线程池对拒绝任务的处理策略
block queue有以下几种实现:
1. ArrayBlockingQueue:有界的数组队列
2. LinkedBlockingQueue:可支持有界、无界的队列,使用链表实现
3. PriorityBlockingQueue:优先队列,可对任务排序
4. SynchronousQueue:队列长度为1的队列,和Array有点区别就是:client 线程提交到 block queue会是一个阻塞过程,直到有一个消费线程连接上来poll task
RejectExecutionHandler是针对任务无法处理时的一些自我保护处理:
1.    Reject 直接抛出Reject exception
2.    Discard 直接忽略该runnable,不建议使用
3.    DiscardOldest 丢弃最早入队列的任务
4.    CallerRuns 直接让原先的client thread做为消费线程,象同步调用方式一样,自己来执行。

如何确定最大线程数?

确定线程数首先需要考虑到系统可用的处理器核心数:

Runtime.getRuntime().availableProcessors(); 应用程序最小线程数应该等于可用的处理器核数。

如果所有的任务都是计算密集型的,则创建处理器可用核心数这么多个线程就可以了,这样已经充分利用了处理器,也就是让它以最大火力不停进行计算。创建更多的线程对于程序性能反而是不利的,因为多个线程间频繁进行上下文切换对于程序性能损耗较大。

如果任务都是IO密集型的,那我们就需要创建比处理器核心数大几倍数量的线程。为何?当一个任务执行IO操作时,线程将被阻塞,于是处理器可以立即进行上下文切换以便处理其他就绪线程。如果我们只有处理器核心数那么多个线程的话,即使有待执行的任务也无法调度处理了。

因此,线程数与我们每个任务处于阻塞状态的时间比例相关。加入任务有50%时间处于阻塞状态,那程序所需线程数是处理器核心数的两倍。我们可以计算出程序所需的线程数,公式如下:

线程数=CPU可用核心数/(1 - 阻塞系数),其中阻塞系数在在0到1范围内。

计算密集型程序的阻塞系数为0,IO密集型程序的阻塞系数接近1。 确定阻塞系数,我们可以先试着猜测,或者采用一些性能分析工具或java.lang.management API 来确定线程花在系统IO上的时间与CPU密集任务所耗的时间比值。

  • Executors
    工具类,提供大量管理线程执行器的工厂方法。
    newFixedThreadPool(int) ,创建固定大小的线程池
    newSingleThreadPool(),创建大小为1的线程池,同一时刻执行的task只有一个,其它的都放在阻塞队列中。
    newScheduledThreadPool(int),适用于一些需要定时或延迟的任务。与Timer的区别: Timer是单线程,一旦一个task执行慢,将会影响其它任务。另外如果抛出异常,其它任务也不再执行。 ScheduledThreadPoolExecutor可执行callable的task,执行完毕后得到执行结果。任务队列是基于DelayedWorkQueue实现,将有新task加入时,会按执行时间排序。
  • FutureTask
    用于异步获取执行结果或取消执行任务。通过传入Callable给FutureTask,直接调用run方法执行,之后可以通过FutureTask的get异步方法获得执行结果。FutureTask即使多次调用了run方法,它只会执行一次Callable任务,当然也可以通过cancel来取消执行。 《分布式java应用》P158
  • ArrayBlockingQueue
    基于数组、先进先出、线程安全的集合
  • CopyOnWriteArrayList
    线程安全,读操作时无锁的ArrayList。每次新增一个对象时,会将创建一个新的数组(长度+1),将之前的数组中的内容复制到新的数组中,并将新增的对象放入数组末尾。最后做引用切换。
  • CopyOnWriteArraySet
    与上面的类似,无非在add时,会调用addIfAbsent,由于每次add时都要进行数组遍历,因此性能会略低于CopyOnWriteArrayList
  • ReentrantLock
    单锁。控制并发的,和synchronized达到的效果是一致的。 Lock方法,借助于CAS机制来控制锁。 Unlock方法,释放锁
  • ReentrantReadWriteLock
    与ReentrantLock没有任何继承关系,提供了读锁和写锁,在读多写少的场景中大幅度提升性能。
    持有读锁时,不能直接调用写锁的lock方法  持有写锁时,其他线程的读或写都会被阻塞。
    ReentrantReadWriteLock lock=new ReentrantReadWriteLock(); WriteLock writeLock=lock.writeLock(); ReadLock readLock=lock.readLock(); 《分布式java应用》P165
  • 如何避免死锁
    1.制定锁的顺序,来避免死锁(先A后B,避免A->B和B->A同时存在);
    2.尝试使用定时锁(lock.tryLock(timeout))
    3.在持有锁的方法中进行其他方法的调用,尽量使用开放调用(当调用方法不需要持有锁时,叫做开放调用)
    4.减少锁的持有时间、减小锁代码块的粒度。
相关文章
|
4月前
|
安全 Java API
JDK 11中的动态类文件常量:探索Java字节码的灵活性与动态性
在JDK 11中,Java语言引入了一个新的特性,允许在运行时动态地修改类文件常量。这一特性为Java开发者提供了更大的灵活性,使他们能够根据需要在运行时更改类文件中的常量值。本文将深入探讨动态类文件常量的工作原理、优点、限制以及在实际项目中的应用。
123 11
|
4月前
|
Java 调度 开发者
JDK 21中的虚拟线程:轻量级并发的新篇章
本文深入探讨了JDK 21中引入的虚拟线程(Virtual Threads)概念,分析了其背后的设计哲学,以及与传统线程模型的区别。文章还将讨论虚拟线程如何简化并发编程,提高资源利用率,并展示了一些使用虚拟线程进行开发的示例。
|
4月前
|
存储 缓存 并行计算
【面试问题】JDK并发类库提供的线程池实现有哪些?
【1月更文挑战第27天】【面试问题】JDK并发类库提供的线程池实现有哪些?
|
4月前
|
Java
Integer类【JDK源码分析】
Integer类【JDK源码分析】
39 0
|
4天前
|
Java Linux
java基础(3)安装好JDK后使用javac.exe编译java文件、java.exe运行编译好的类
本文介绍了如何在安装JDK后使用`javac.exe`编译Java文件,以及使用`java.exe`运行编译好的类文件。涵盖了JDK的安装、环境变量配置、编写Java程序、使用命令行编译和运行程序的步骤,并提供了解决中文乱码的方法。
17 1
|
20天前
|
监控 Java 开发者
【并发编程的终极简化】JDK 22结构化并发:让并发编程变得像写代码一样简单!
【9月更文挑战第8天】随着JDK 22的发布,结构化并发为Java编程带来了全新的并发编程体验。它不仅简化了并发编程的复杂性,提高了程序的可靠性和可观察性,还为开发者们提供了更加高效、简单的并发编程方式。我们相信,在未来的发展中,结构化并发将成为Java并发编程的主流方式之一,推动Java编程语言的进一步发展。让我们共同期待Java在并发编程领域的更多创新和突破!
|
23天前
|
Java API 开发者
【Java字节码操控新篇章】JDK 22类文件API预览:解锁Java底层的无限可能!
【9月更文挑战第6天】JDK 22的类文件API为Java开发者们打开了一扇通往Java底层世界的大门。通过这个API,我们可以更加深入地理解Java程序的工作原理,实现更加灵活和强大的功能。虽然目前它还处于预览版阶段,但我们已经可以预见其在未来Java开发中的重要地位。让我们共同期待Java字节码操控新篇章的到来!
|
20天前
|
Java API 开发者
【Java字节码的掌控者】JDK 22类文件API:解锁Java深层次的奥秘,赋能开发者无限可能!
【9月更文挑战第8天】JDK 22类文件API的引入,为Java开发者们打开了一扇通往Java字节码操控新世界的大门。通过这个API,我们可以更加深入地理解Java程序的底层行为,实现更加高效、可靠和创新的Java应用。虽然目前它还处于预览版阶段,但我们已经可以预见其在未来Java开发中的重要地位。让我们共同期待Java字节码操控新篇章的到来,并积极探索类文件API带来的无限可能!
|
1月前
|
存储 Java
【Java集合类面试七】、 JDK7和JDK8中的HashMap有什么区别?
JDK7中的HashMap使用数组加链表解决冲突,而JDK8增加了红黑树结构以优化链表过长时的性能,提高查找效率。
|
1月前
|
Oracle 安全 Java
JDK8到JDK28版本升级的新特性问题之在Java 15及以后的版本中,密封类和密封接口是怎么工作的
JDK8到JDK28版本升级的新特性问题之在Java 15及以后的版本中,密封类和密封接口是怎么工作的