Java并发编程

2019/01/20 22:56 下午 posted in  Java

  本文对Java中的并发变成进行了简单的描述。是本人阅读《Java并发编程的艺术》一书的读书笔记。本文对重要的概念进行了记录。

  本文首先介绍了各种各样的和锁相关的概念。然后介绍了Java多线程的技术要点,最后介绍了一些经典使用案例。

0. 重要技术要点目录

0.1 和锁有关的概念

  关于锁的概念有很多,这里一一列举。

按照锁的的状态分

  在Java中,按照锁的状态分,有四种状态。分别是:

  1. 无锁
  2. 偏向锁
  3. 轻量级锁
  4. 重量级锁

偏向锁是可以取消的,根据系统的实际使用情况。因为可能有的场景,系统中资源冲突严重,那么偏向锁就没有什么必要存在了,反而增加处理逻辑,更慢。

按照锁的实现方式分

  1. 乐观锁
  2. 悲观锁

  在Java中没有特地讲乐观锁,悲观锁。但实际上这两种锁是可以和Java中提到的概念一一对应。

按照试用场景分

  1. 可重入锁--ReentranLock
  2. 读写锁--ReentranReadWriteLock

  这两种锁是可以直接和Java中的实现对应的。

其他

  • volatile和synchronized

  这两个关键字是Java锁的基础(很多其他语言也有相同的使用)。例如可重入锁和读写锁其实都是对这两个关键词的应用,并不是另起炉灶。

  • CAS操作

  CAS(Compare and swap)是一种乐观锁的操作方式。CAS操作保证了线程安全,同时没有对线程加锁。是一种常见的优化锁使用的选择。

  • happens-before原则

指令重排的相关概念。指的是按照编码逻辑的指令的执行顺序。A指令happens-beforeB指令。那么一个重要的原则是,判断是否可以重排的依据,并不是要求指令一定要按照物理顺序。只要重排后的指令执行结果和重排前一致就可以。

1. Java中的几种锁

读写锁
重入锁

2. Java中的锁的状态

锁会升级不会降级。越“轻”的锁,效率越高。但是越不好解决同步的问题(不能解决的时候就要升级)。
偏向锁就是偏向于拿锁的线程通常是同一个,这样就可以提高效率。

Q:红框处为什么要检查线程是否活着?
A:线程1执行完毕后,不会主动去释放偏向锁。

Q:偏向锁是如何升级成轻量级锁的?
A:当另一个线程获取偏向锁失败,就升级成轻量级锁。轻量级锁的特点是自旋。

Q:轻量级锁是如何升级成重量级锁的?
A:轻量级锁自旋到达一定条件就会升级成重量级锁。但是具体条件是啥,没有找到相关资料。

总结,获取不到锁就会往上膨胀。直到重量级锁。

通俗来讲就是:

偏向锁:仅有一个线程进入临界区
轻量级锁:多个线程交替进入临界区
重量级锁:多个线程同时进入临界区

问题
轻量级锁是自旋的,那么根据什么条件判断会膨胀成重量级锁

3. volatile

使用volatile修饰变量,可以让这个变量在各个线程之间永远显示的是最新的值。为什么会有这么一个说法?每一个线程有自己的独立缓存。对于一个共享变量,在多线程并发的情况下,可能导致自己的缓存和实际的值不等。而volaile的出现,使得线程自己的缓存无效,每一次都需要去内存中读取最新的值。
volatile可以看做轻量的synchronized。可以减少锁的程度。提高性能。

3. volatile实现原理的相关知识

  1. happens-before原则,涉及到volatile的底层实现原理。

4. happens-before原则

所以本质的意思就是指令重排不能影响计算结果。只要计算结果一样,虽然有happens-before关系,也不要求一定要按照happens-before的顺序执行

volatile的经典应用-单例模式

wait()和notify()

  1. 作用

  2. 好处。
    wait() 等待。相比一个死循环,定期休眠来判断是否结束要好的多,因为定期休眠可能会导致响应不及时。

  3. notify() 之后,并不是对应的wait会立刻结束

ThreadLocal

线程池技术

几种线程池的创建方式,以及适应的场景。

锁和synchronized的关系

ReentrantLock 和 ReetranReadWriteLock 可重入锁和可重入读写锁

可重入的意思是可以被同一个线程重复加锁。
读写锁适用于生产者消费者场景。分为读锁和写锁。

Object的wait,notify和condition的await,signal

ConcurrentHashMap分析

使用了锁来保证线程安全。使用了锁分段技术。减少了锁的冲突,从而提高了效率。

ConcurrentLinkedQueue分析

学习其特别的尾节点定义方式。可以很大程度减少冲突。其使用CAS操作来避免冲突。但是其特别的尾节点定义方式,使得CAS操作不那么容易失败。

出队也有相似的逻辑

Java中的阻塞队列

DelayQueue

可以支持延时获取元素的队列,要求队列中的元素必须实现Delayed接口

  • 经典场景

缓存系统

定时任务调度

SynchronousQueue

LinkedBlockingQueue

用在工作窃取模式

CountDownLatch join的升级版

Semaphore

线程池

  1. 队列模型的选取
    1. 建议使用有界队列
    2. 建议和系统的核心数挂钩??
  2. 饱和策略的选取和试用场景

乐观锁,悲观锁

乐观锁就是CAS操作

悲观锁就是synchronized

乐观锁认为读大于写,不用一下子就把线程锁了。而是尝试一下。

悲观锁认为系统冲突严重,必须锁。

CAS竞争锁
CAS操作
偏向锁
轻量级锁
乐观锁
悲观锁
volatile 和 ++
threadLocal
threadpool
condition 是和lock相配合的
相比wait,notify是和synchronized配合的

锁分段技术,代表concurrentHashMap

各种queue的特点,区别,和使用场景。最好有范例。各种queue的试用场景

线程池

结合阿里的java开发手册中关于线程池的部分结合理解

工作线程worker

threadLocal 和 volatile变量