JAVA系列-并发

1. 锁

1) Lock与synchronized的区别

  • Lock是接口,而synchronized是java中的关键字;
  • synchronized不会导致死锁现象发生,而Lock可能造成死锁现象;
  • Lock可以让等待锁的线程响应中断,而synchronized却不行;
  • 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到;
  • Lock可以提高多个线程进行读操作的效率;
  • 在性能上来说,如果竞争资源不激烈,两者的性能差不多,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
总结:这两者的使用在具体使用时要根据适当情况选择

2)公平锁

  • ReentrantLock 设置公平锁true

3) 非公平锁

  • ReentrantLock 默认非公平锁,吞吐量比公平锁大

4) 可重入锁(递归锁)

  • ReentrantLock
  • synchronized

5) 独占锁(写锁)/独享锁(读锁)/互斥锁

独占锁:指该锁只能被一个线程所有;

  • ReentrantLock
  • Synchronized

共享锁:指该锁可被多个线程所持有;

  • ReentrantReadWriteLock其读锁是共享锁,写锁是独占锁

读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。

6)自旋锁

尝试获取锁的线程不会立即阻塞

JAVA系列-并发

6)分布式锁

(1)具备特性

  • 互斥性
  • 可重入性
  • 锁超时高效
  • 高可用
  • 支持阻塞和非阻塞
  • 支持公平锁和非公平锁

(2)实现分布式锁的几种方案

  • 数据库实现(悲观与乐观锁)
  • 基于Zookeeper实现
  • 基于Redis实现
  • 自研分布式锁:如谷歌的Chubby
分布式锁方案比较:
  • 理解难易程序:数据库 < 缓存 < zookeeper
  • 实现复杂度:zookeeper <= 缓存 < 数据库
  • 性能:缓存 > zookeeper >= 数据库
  • 可靠性:zookeeper > 缓存 > 数据库

(3)基于Redis分布锁

实现Redisson

7. 死锁

死锁的概念:

指两个或两上以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。

JAVA系列-并发

产生死锁的原因:

  • 系统资源不足
  • 进程运行推进的顺序不合适
  • 资源分配不当

查询java死锁现象的常用命令

  • jps命令定位进程号: jps -l
  • jstack找到死锁查看: jstack 进程号

2. 多线程

1)线程池

线程池的几大参数(7大参数)

  • corePoolSize: 线程池中常驻核心线程数
  • maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1
  • keepAliveTime:
    多余的空闲线程的存活时间(当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止)
  • unit: keepAliveTime的单位
  • workQueue: 任务队列,被提交但尚未被执行的任务
  • threadFactory: 表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可
  • handler: 拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)
    • AbortPolicy
    • DiscardPolicy
    • CallerRunsPolicy
    • DiscardOldestPolicy

线程池的底层工作原理

JAVA系列-并发


JAVA系列-并发

JAVA系列-并发

线程池的拒绝策略

JDK内置的拒绝策略(均实现了RejectedExecutionHandler接口)
  • AbortPolicy(默认): 直接抛出RejectedExecutionException异常阻止系统正常运行;
  • CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量;
  • DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务;
  • DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案;

选择哪一个线程池

阿里java手册说明

【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问 题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors 返回的线程池对象的弊端如下:

  • FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
  • CachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

合理的配置线程池(maximumPoolSize

CPU密集型:
  • 说明:CPU密集一般该任务需要大量的运算,而没有阻塞,CPU一直全速运行,CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程)
  • 建议:CPU密集型任务配置尽可能少的线程数量
  • 参考公式:CPU核数+1个线程的线程池
IO密集型:
  • 说明:IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程IO密集型时,大部分线程都阻塞,故需要多配置线程数
  • 参考公式:CPU核*2CPU核数 / (1 - 阻塞系数) 阻塞系数在0.8~0.9之间

2) 线程安全

(1) 集合

a. 线程不安全:
  • ArryList
  • HashSet
  • HashMap

多线程写情况下会抛ConcurrentModificationException异常

b. 线程安全
  • Vector
  • collections.synchronizedxxx
  • CopyOnWriteArrayxxx
  • ConcurrentMap
c. 解决方案:
  • 同步向量:new Vector()
  • 加锁数组:collections.synchronizedList(new ArrayList())
  • 写时复制:new CopyOnWriteArrayList()

3. 并发相关概念

1)volatile

Java虚拟机提供的轻量级的同步机制

  • 可见性
  • 不保证原子性
  • 禁止指令重排

2) JMM(Java内存模型)

要求的特性

  • 可见性
  • 原子性
  • 有序性

3)CAS (Compare and set )

AtomXX-相关类

缺点:

  • 1. 循环时间长,开销很大(主要是CPU开销)
  • 2. 只能保证一个共享变量的原子操作
  • 3. 引发ABA问题

4)ABA问题

  • 原子引用(AtomicReference):时间戳的原子引用(AtomicStampedReference)
  • 规避ABA问题

5)ThreadLocal

  • ThreadLocal 面试六连问,你能 Hold 住吗?
  • 深入剖析ThreadLocal
  • 深入分析 ThreadLocal 内存泄漏问题
  • 使用 ThreadLocal 变量的时机和方法


分享到:


相關文章: