Java并发包JUC

原子性操作–Atomic 原子性

  1. 原子性操作实际上就是针对属性来提供线程安全的方法,在底层会自动采用CAS来保证线程安全。
  2. volatile是Java提供的轻量级的线程同步机制

阻塞队列 BlockingQueue

原则:

  1. 遵循FIFO
  2. 往往是有界的,容量固定不变
  3. 具有阻塞特性:如果队列已满,则试图放入的线程会被阻塞;如果队列为空,就会阻塞尝试获取的线程
  4. 不允许元素null

方法:

  1. 抛出异常返回特殊值产生阻塞定时阻塞
    添加add - IllegalStateExceptionoffer - falseputoffer
    获取remove - NoSuchElementExceptionpoll - nulltakepoll

ConcurrentMap -并发映射

  1. ConcurrentHashMap底层基于数组h+链表来存储数据,数组的每一个位置称之为桶
  2. 默认初始容量16(桶的数量),加载因子0.75
  3. 如果指定容量,底层会进行计算,实际容量一定是2的n次方
  4. 每次扩容每次增加一倍的桶数,扩容之后需要rehash操作
public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
    }
//底层算法
private static final int tableSizeFor(int c) {
        int n = c - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
  1. 分段锁,封桶锁机制,不是把整个映射锁起来
  2. 在jdk1.7引入读写锁

ConcurrentNavigableMap - 并发导航映射

  1. ConcurrentNavigableMap提供了用于截取子映射的方法
  2. ConcurrentNavigableMap本身是一个接口,所以更多的是使用它的实现类ConcurrentSkipListMap - 并发跳跃表映射

跳跃表

  1. 针对有序集合
  2. 适合于增删少,查询多的场景
  3. 跳跃表可以进行多层提取,最上层的跳跃表的元素个数不能少于2个
  4. 跳跃表是典型的“以空间换时间“的产物
  5. 在跳跃表中新添元素提取到上层跳跃表,遵循抛硬币原则
  6. 跳跃表的时间复杂度是O(logn)

ExecutorService

概述

  1. 本质上是一个线程池
    线程池:减少服务器的线程的创建和销毁,做到连接的复用
  2. 刚开始创建时空的,里面不包含任何线程
  3. 每接受一个请求,线程池都会创建一个线程(核心线程)来处理这个请求,在创建线程的时候就需要指定线程的数量
  4. 在核心线程达到指定数量之前,来一个请求就会创建一个新的线程来处理这个请求,直到线程满了就不再创建
  5. 核心线程处理完这个请求不会销毁,等待下一个请求
  6. 如果核心线程全部沾满,新来的请求会暂存在工作队列,工作队列本身是一个阻塞式队列
  7. 当核心线程有空的时候,就会从工作队列中取出交给核心线程处理
  8. 如果工作队列被全部占用,有接受到新的请求,会将这个请求叫个临时线程来处理
  9. 当临时线程处理完请求之后不会立即结束,而是会存活一段时间,如果在这个时间段内接受到新的请求,会处理新的请求,如果没有接收到请求,就会被kill掉
  10. 临时线程不会从工作队列中拿出请求处理
  11. 当临时线程全部占用,那么新来的请求就会交给执行助手来拒绝处理,在实际开发中,如果需要拒绝请求,可能会产生多部操作,例如记录日志,页面跳转,请求重发
  12. ScheduledExecutorService:定时调度执行器服务。在线程池的基础上加入了定时调度效果。这个线程池本身是很多定时调度机制的底层实现

内存模型

栈内存:执行方法- 计算 栈内存是线程独享的
堆内存:存储对象,线程共享
方法区:存储类信息(字节码,静态,常量)。线程共享的
本地方法栈:执行本地方法,线程独享
本地方法是指在Java中用native声明但使用其他语言实现的方法
pc计数器/寄存器:指令计数,线程独享

如果需要计算一台服务器的线程承载量,考虑线程独享部分。
在jdk1.8中,栈内存最小128k
pc计数器一般只占几个字节,可以忽略

红黑树

定义

a. 红黑树本质上一棵自平衡二叉查找树

b.特征:

  • i. 所有节点的颜色非红即黑
  • ii. 根节点为黑色
  • iii. 红节点的子节点一定黑节点,黑节点的子节点可以是红节点也可以是黑节点
  • iv. 最底层的叶子节点一定是黑色的空节点
  • v. 从根节点到任意一个叶子节点的路径中经过的黑色节点个数一致,即黑节点高度是一致的
  • vi. 新添的节点颜色一定是红的

c. 修正:

  • i. 涂色:父子节点为红,叔父节点为红,将父节点核叔父节点涂成黑色,然后将祖父节点涂成红色
  • ii. 左旋:父子节点为红,叔父节点为黑,且子节点是右子叶,那么以子节点为轴进行左旋
  • iii. 右旋:父子节点为红,叔父节点为黑,且子节点是左子叶,那么以父节点为轴进行右旋

d. 红黑树的时间复杂度是O(logn)

Callable

定义:

  1. Callable是Java提供的一种定义线程的方式,在使用的时候通过泛型执行返回值类型

Callable和Runnable的区别

RunnableCallable
返回值没有返回值通过泛型来指定返回值的类型
启动方式1. 通过Thread的start方法启动 2. 通过线程池的submit或者是execute方法执行通过线程池的submit方法执行
容错机制不允许抛出异常,所以不能利用全局方式(例如Spring中的异常通知)处理允许抛出异常,所以可以利用切面或者是全局方式来处理异常

分叉合并池

  1. Fork:分叉。将一个大任务拆分成多个小任务交给多个线程执行
  2. Join:合并。将拆分出来的小任务的执行结果进行汇总
  3. 分叉合并的目的是为了提高CPU的利用率
  4. 在数据量相对小的时候,循环会比分叉合并快;数据量越大,分叉合并的优势越明显
  5. 分叉合并在进行的时候,导致其他程序的执行效率显著降低,所以分叉合并一般是在周末的凌晨来进行
  6. 分叉合并在分配子线程的时候,尽量做到每个核上的任务均匀:少的多分,多的少分
  7. 在分叉合并中,为了减少"慢任务"带来的效率降低,采取"work-stealing"(工作窃取)策略:当一个核将它的任务队列处理完成之后,这个核并不会闲下来,会随机扫描一个核,然后从被扫描核的任务队列尾端"偷取"一个任务回来执行

Lock - 锁

概述

  1. 相对于synchronized更加灵活和精细
  2. ReadWriteLock - 读写锁
  • a. 在使用的时候,需要用这个接口的实现类ReentrantReadWriteLock
  • b. 在加锁的时候,需要先通过对应的方法来获取读锁或者写锁
  1. 公平和非公平策略
  • a. 非公平策略下,线程会直接抢占执行权,在资源有限的前提下,线程之间抢到的次数不一样
  • b. 公平策略下,线程不是直接抢占执行权,而是去抢占入队顺序。宪曾之间的执行次数是基本一致的
  • c. 默认情况下,使用的是非公平策略
  • d. 相对而言,非公平策略的效率会更高一些

其他

  1. CountDownLatch:闭锁/线程递减锁。对线程进行计数,在计数归零之前会让线程陷入阻塞,直到计数归零才会放开阻塞 - 一波线程结束之后开启另一波线程
  2. CyclicBarrier:栅栏。对线程进行计数,在计数归零之前让线程陷入阻塞,直到计数归零为止才会放开阻塞 - 线程到达同一个地点之后再分别执行
  3. Exchanger:交换机。用于交换2个线程之间的信息
  4. Semaphore:信号量。线程只有获取到信号之后才能执行,执行完成之后需要释放信号。如果所有的信号都被占用,那么后来的线程就会被阻塞

版权声明:本文为Niklaus1028原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。