JUC提供了CountDownLatch、CycelicBarrier、Semaphore、Exchanger等几种并发工具。信号量,又称计数信号量,初始化并维护一定数量的许可。使用前必须先获取license,用完再释放license。当然,也可以反过来使用。构造Semaphore时,传入0个licenses,执行完一个操作后添加一个license,然后释放等待线程。信号量通常用于限制线程数来控制对某些资源的访问,从而达到单机限流的目的。Semaphore也是基于AQS框架实现的。信号量也有公平和不公平。Semaphore支持重入获取权限。CountDownLatch允许一个或多个线程等待其他线程完成操作。在初始化时给定一个CountDownLatch计数,调用await()方法的线程将永远等待,并在其他线程执行操作后调用countDown()。当计数减到0时,调用await()方法的线程会被唤醒继续执行。CountDownLatch没有增加计数的API,所以它不能被重用。如果要重置计数,可以使用CyclicBarrier。CountDownLatch可以用在多个线程同时操作自己的逻辑等业务场景,全部完成后再统一汇总。Exchanger用于线程间的数据交换。它提供了一个同步点。在这个同步点,两个线程可以交换数据,主要是通过exchange()方法。如果第一个线程先执行exchange()方法,它会一直等待第二个线程。exchange()方法也被执行,当两个线程都到达同步点时,这个线程产生的数据就可以传递给对方了。Exchanger中有一个内部类Node,用sun.misc.Contended注解修饰。由于最大cacheline为128字节,使用sun.misc.Contended增加padding(虚拟机参数-XX:-RestrictContended支持),可以让任意两个可用的Node不在同一个cacheline中。除了Exchanger直接使用Unsafe中的park和unpark进行线程等待外,其他三个类都是借助AQS完成的。Exchanger源码可以参考:源码分析:Exchanger的数据交换器JUCJUC封装分为以下几类:原子型、基本型、引用型、累加器锁、读写锁、可重入锁集合并发容器(或非阻塞)Queue)ConcurrentHashMap/ConcurrentSkipListMap和阻塞队列BlockingQueue/CopyOnWriteArrayListexecutor执行框架和线程池,Future/Executortools并发工具类,CountdownLatch/CyclicBarrier/Semaphore/Exchanger非阻塞队列ConcurrentLinkedQueue采用CAS非阻塞算法+不停机重试实现线程安全,适用于不需要闭锁功能时使用;阻塞队列BlockLinkedQueue主要使用Lock和Condition来等待唤醒。AQS1、state属性AQS中有一个重要的字段state,在不同的类中有不同的含义:ReentrantLock,基于state实现的排他锁,state值为1表示锁被占用,值为0表示锁没有被占用。重入锁状态是重入次数。基于状态实现的读写锁ReentrantReadWriteLock,状态分为两部分,高16位记录读锁数,低16位记录写锁数。读锁中不同线程获取读锁的个数保存在threadlocal中。基于state实现的信号量Semaphore,初始化一个state值,表示最大限制个数,即最多可以允许N个线程同时运行,达到限流效果。基于状态实现的线程等待器CountDownLatch初始化一个状态值,当状态值为0时触发唤醒动作。2.两个队列AQS中有两个重要的队列:同步队列和等待队列。同步队列:维护唤醒线程的队列,获取互斥量失败时将线程入队。等待队列:实现条件锁时使用的队列。当调用await()时,锁将被释放,然后线程将被添加到条件队列中。当调用signal()唤醒时,条件队列中的线程节点会被移到同步队列中,等待再次获取锁。3、节点状态AQS定义了五个队列中节点的状态:值为0,初始化状态,表示当前节点在sync队列中,等待获取锁。CANCELLED,值为1,表示当前线程被取消。SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,即unpark。CONDITION,值为-2,表示当前节点正在等待条件,即在条件队列中。PROPAGATE,值为-3,表示后续的acquireShared可以在当前场景执行。然后记录几个API:Thread的Sleep()方法:必须指定时间;需要捕获异常;不会释放锁的Object的wait()方法和notify()方法:必须配合sychronized关键字使用;notify()方法在wait()方法之后执行,否则唤醒信号会丢失;需要捕获异常;锁将被释放。Condition接口的await()和Signal()方法:必须配合lock.lock()方法使用:主要用于定点唤醒。将释放锁。LockSupport的park()和unpark(thread)方法:不需要捕获异常;锁不会被释放;可以通过unpark唤醒,unpark可以在park之前执行,唤醒信号不会丢失。再提一下park,park会阻塞当前线程,下面四种情况会返回:对应的unpark已经执行,线程可以在等待时间前后中断,直到出现异常,最后有一个整个锁的使用描述,如果仔细考虑锁的应用范围后性能不能满足要求,我们需要考虑另一个维度的粒度,即:区分读写场景和资源访问冲突,以及考虑是使用悲观锁还是乐观锁。(参考:《Java业务开发常见错误100例》)在一般业务中:使用锁时,锁的作用范围尽量小。对于读写比例差异较大的场景,可以考虑使用ReentrantReadWriteLock微调读写锁的区分来提升性能。如果你的JDK版本高于1.8,共享资源的冲突概率不是那么高,可以考虑使用StampedLock的乐观读特性,进一步提升性能。JDK中的ReentrantLock和ReentrantReadWriteLock都提供了公平锁的版本。不要在没有明确需要的情况下启用公平锁定功能。在任务轻时启用公平锁可能会使性能降低数百倍。参考文章:《Java并发编程的艺术》AbstractQueuedSynchronizer(AQS)总结
