10 ConcurrentHashMap

ConcurrentHashMap 的初步使用及场景
 
CHM 的使用
ConcurrentHashMap 是 J.U.C 包里面提供的一个线程安全并且高效的 HashMap,所以
ConcurrentHashMap 在并发编程的场景中使用的频率比较高,那么这一节课我们就从
ConcurrentHashMap 的使用上以及源码层面来分析 ConcurrentHashMap 到底是如何实现安全性的api 使用
 
ConcurrentHashMap 是 Map 的派生类,所以 api 基本和 Hashmap 是类似,主要就是 put、get 这些方法,接下来基于 ConcurrentHashMap 的 put 和 get 这两个方法作为切入点来分
析 ConcurrentHashMap 的源码实现.
 
 

ConcurrentHashMap的锁分段技术(数据分段存储,分段枷锁)

首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

数据结构:

 

 

 

 

ConcurrentHashMap为了提高本身的并发能力,在内部采用了一个叫做Segment的结构,一个Segment其实就是一个类Hash Table的结构,Segment内部维护了一个链表数组。当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

 

高并发:ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部。在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上),所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。总的Map包含了16个Segment(默认数量),每个Segment内部包含16个HashEntry(默认数量),这样对于这个key所在的Segment加锁的同时,其他15个Segmeng还能正常使用,在性能上有了大大的提升。

 

详细解释一下Segment里面的成员变量的意义:

count:Segment中元素的数量

modCount:对table的大小造成影响的操作的数量(比如put或者remove操作)

threshold:阈值,Segment里面元素的数量超过这个值依旧就会对Segment进行扩容

table:链表数组,数组中的每一个元素代表了一个链表的头部

loadFactor:负载因子,用于确定threshold

 

volatile的保证:对volatile域的写入操作happens-before于每一个后续对同一个域的读写操作。所以,每次判断count变量的时候,即使恰好其他线程改变了segment也会体现出来。

 

get方法没有使用锁来同步,只是判断获取的entry的value是否为null,为null时才使用加锁的方式再次去获取。

put 操作:首先对Segment的put操作是加锁完成的。因为每个HashEntry中的next也是final的,没法对链表最后一个元素增加一个后续entry所以新增一个entry的实现方式只能通过头结点来插入了。

remove 操作:先定位Segment的过程,然后确定需要删除的元素的位置, 程序就将待删除元素前面的那一些元

素全部复制一遍,然后再一个一个重新接到链表上去,

 
 
 
 

 

多线程知识点问答

 

1创建线程的几种方式? Wait,sleep分别是谁的方法,区别?线程间的通信方式?

继承Thread类创建线程,实现Runnable接口创建线程,使用Callable和Future创建线程 ,使用线程池例如用Executor框架。   Wait是在Object.java中的方法,sleep是在Thread.java中的方法。 区别是wait是当前运行线程进入阻塞状态并释放锁,sleep使线程进入阻塞状态但不释放锁。   使用wait和notify实现线程间的通信。

 

2 介绍下什么是死锁,遇见过死锁么?你是怎么排查的?(可以通过JPS排查)

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,通过jps排查。

 

3 创建线程池的几种方式,线程池有什么好处?

单线程的线程池  固定大小的线程池 一个可缓存的线程池 一个大小无限的线程池

频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。线程池可以使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务。

 

4 线程继承和接口的区别,接口有什么好处?

java中我们想要实现多线程常用的有两种方法,继承Thread 类和实现Runnable 接口,有经验的程序员都会

选择实现Runnable接口 ,其主要原因有以下两点:首先,java只能单继承,因此如果是采用继承Thread的方法,那么在以后进行代码重构的时候可能会遇到问题,因为你无法继承别的类了。其次,如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

 

5 synchronized ,lock ,reentrantlock 的区别,用法及原理

ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁;ReenTrantLock提供了一个Condition(条件)类,可实现分组唤醒线程,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程;ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。synchronized不可中断

6 countDownLatch 与 CyclicBarrier 的用法

CountDownLatch的计数器,线程完成一个记录一个,计数器是递减 计数器,只能使用一次;CyclicBarrier的计数器 更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行,计数器是递增 计数器提供reset功能,可以多次使用

 

7 ThreadLocal 的用法及原理

ThreadLocal用处就是用来把实例变量共享成全局变量,在程序的任何方法中都可以访问到该实例变量;

线程隔离的原理, ThreadLocalMap类是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取,每个线程中都有一个独立的ThreadLocalMap副本(key-ThreadLocal,value-副本),它所存储的值(副本),只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了专属变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。

 

8 volatile 关键字的作用及用法

  保证内存可见性 volatile关键字用于声明简单类型变量,如intfloat boolean等数据类型。如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的。

 

9 乐观锁和悲观锁

 

 

10 对公平锁,非公平锁,可重入锁,自旋锁,读写锁的理解

锁是基于线程的分配,可重入锁: 可以被单个线程多次获取。可中断锁: 某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,种就是可中断锁。 公平锁:即尽量以请求锁的顺序来获取锁。自旋锁,一个线程A在获得普通锁后,如果再有线程B试图获取锁,那么这个线程B将会(阻塞)。读写所:读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁,正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。

 

11 CAS是什么及底层原理

CAS是一条CPU并发原语。判断内存某个位置的值是否为预期值,如果是更改为新值,这个过程是原子的。

底层原理:Unsafe类是CAS的核心类,由于java方法无法直接访问底层系统,需要通过本地(native)方法来访问,基于该类可以直接操作特定内存的数据。Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务

12 ArrayBlockingQueue,LinkedBlockQueue,SynchronousQueue等等堵塞队列的理解

SynchronousQueue是无界的,是一种无缓冲的等待队列,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加;可以认为SynchronousQueue是一个缓存值为1的阻塞队列

LinkedBlockingQueue是无界的,是一个无界缓存的等待队列。当队列缓冲区达到最大值缓存容量时,才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。

在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

ArrayBlockingQueue是有界的,是一个有界缓存的等待队列。在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;ArrayBlockingQueue和LinkedBlockingQueue是两个最普通、最常用的阻塞队列,一般情况下,处理多线程间的生产者消费者问题,使用这两个类足以。

 

13 ThreadPoolExecutor 的传入参数及内部工作原理

14 给你一个具体的业务场景,让你使用ThreadPoolExecutor 创建一个合适的线程池

15 分布式环境下怎么保证线程安全。

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
posted @ 2020-02-10 15:27  LPJのBLOG  Views(152)  Comments(0)    收藏  举报