java八股文复习----java集合,CAS---2024/03/12

1.java常见的集合类

在这里插入图片描述

2.List,Set,Map的区别

在这里插入图片描述

3.上述三个集合有哪些常用的方法

在这里插入图片描述

4.List,Set,Map哪几个是线程安全的?

在这里插入图片描述

5.ArrayList和LinkedList的区别

在这里插入图片描述

6.ArrayList和Vector的区别

在这里插入图片描述

7.ArrayList的扩容机制

在这里插入图片描述

8.HashMap集合

8.1数据结构

在这里插入图片描述

8.2哈希冲突的解决办法有哪些

在这里插入图片描述

8.3什么时候使用链表,什么时候使用红黑树?

在这里插入图片描述

8.4为什么使用红黑树而不是二叉树或者平衡二叉树?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

9.HashMap是否线程安全?是如何解决的?

在这里插入图片描述
在这里插入图片描述

10.HashMap的实现原理

1.HashMap在Jdk1.8以后是基于数组+链表+红黑树来实现的,特点是,key不能重复,可以为null,线程不安全

2.HashMap的扩容机制

HashMap的默认容量为16,默认的负载因子为0.75,当HashMap中元素个数超过容量乘以负载因子的个数时,就创建一个大小为前一次两倍的新数组,再将原来数组中的数据复制到新数组中。当数组长度到达64且链表长度大于8时,链表转为红黑树

3.HashMap存取原理

(1)计算key的hash值,然后进行二次hash,根据二次hash结果找到对应的索引位置

(2)如果这个位置有值,先进性equals比较,若结果为true则取代该元素,若结果为false,就使用高低位平移法将节点插入链表(JDK8以前使用头插法,但是头插法在并发扩容时可能会造成环形链表或数据丢失,而高低位平移发会发生数据覆盖的情况)
————————————————

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

原文链接:https://blog.csdn.net/w20001118/article/details/125724647

11.ConcurrentHashMap原如何保证的线程安全?

JDK1.7:使用分段锁,将一个Map分为了16个段,每个段都是一个小的hashmap,每次操作只对其中一个段加锁

JDK1.8:采用CAS+Synchronized保证线程安全,每次插入数据时判断在当前数组下标是否是第一次插入,是就通过CAS方式插入,然后判断f.hash是否=-1,是的话就说明其他线程正在进行扩容,当前线程也会参与扩容;删除方法用了synchronized修饰,保证并发下移除元素安全


CAS补充资料
我们常说的 CAS 自旋锁是什么
CAS(Compare and swap),即比较并交换,也是实现我们平时所说的自旋锁或乐观锁的核心操作。

它的实现很简单,就是用一个旧的预期的值和内存值进行比较,如果两个值相等,就用新的值替换内存值,并返回 true。否则,返回 false。

保证原子操作
任何技术的出现都是为了解决某些特定的问题, CAS 要解决的问题就是保证原子操作。原子操作是什么,原子就是最小不可拆分的,原子操作就是最小不可拆分的操作,也就是说操作一旦开始,就不能被打断,直到操作完成。在多线程环境下,原子操作是保证线程安全的重要手段。举个例子来说,假设有两个线程在工作,都想对某个值做修改,就拿自增操作来说吧,要对一个整数 i 进行自增操作,需要基本的三个步骤:

1、读取 i 的当前值;

2、对 i 值进行加 1 操作;

3、将 i 值写回内存;

假设两个进程都读取了 i 的当前值,假设是 0,这时候 A 线程对 i 加 1 了,B 线程也 加 1,最后 i 的是 1 ,而不是 2。这就是因为自增操作不是原子操作,分成的这三个步骤可以被干扰。如下面这个例子,10个线程,每个线程都执行 10000 次 i++ 操作,我们期望的值是 100,000,但是很遗憾,结果总是小于 100,000 的。

   static int i = 0;
    
   public static void add(){
        i++;
    }
        private static class Plus implements Runnable{

   @Override
        public void run(){
            for(int k = 0;k<10000;k++){
                add();
            }
        }
    }   
 public static void main(String[] args) throws InterruptedException{
        Thread[] threads = new Thread[10];
        for(int i = 0;i<10;i++){
            threads[i] = new Thread(new Plus());
            threads[i].start();
        }

 for(int i = 0;i<10;i++){
            threads[i].join();
   }
    System.out.println(i);
 }

既然这样,那怎么办。没错,也许你已经想到了,可以加锁或者利用 synchronized 实现,例如,将 add() 方法修改为如下这样:

public synchronized static void add(){
        i++;
    }

或者,加锁操作,例如下面使用 ReentrantLock (可重入锁)实现。

private static Lock lock = new ReentrantLock();
    public static void add(){
        lock.lock();
        i++;
        lock.unlock();
    }

CAS 实现自旋锁
既然用锁或 synchronized 关键字可以实现原子操作,那么为什么还要用 CAS 呢,因为加锁或使用 synchronized 关键字带来的性能损耗较大,而用 CAS 可以实现乐观锁,它实际上是直接利用了 CPU 层面的指令,所以性能很高。

上面也说了,CAS 是实现自旋锁的基础,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果,至于自旋呢,看字面意思也很明白,自己旋转,翻译成人话就是循环,一般是用一个无限循环实现。这样一来,一个无限循环中,执行一个 CAS 操作,当操作成功,返回 true 时,循环结束;当返回 false 时,接着执行循环,继续尝试 CAS 操作,直到返回 true。

其实 JDK 中有好多地方用到了 CAS ,尤其是 java.util.concurrent包下,比如 CountDownLatch、Semaphore、ReentrantLock 中,再比如 java.util.concurrent.atomic 包下,相信大家都用到过 Atomic* ,比如 AtomicBoolean、AtomicInteger 等。

12.CAS


什么是CAS

CAS(Compare-and-Swap)是一种乐观锁的实现方式,全称为“比较并交换”,是一种无锁的原子操作。

在并发编程中,我们都知道i++操作是非线程安全的,这是因为 i++操作不是原子操作,我们之前在讲多线程带来了什么问题中有讲到,大家应该还记得吧?

如何保证原子性呢?

常见的做法就是加锁。

在 Java 中,我们可以使用 synchronized关键字 和 CAS(Compare-and-Swap)来实现加锁效果。

**synchronized 是悲观锁,**尽管随着 JDK 版本的升级,synchronized 关键字已经“轻量级”了很多(前面有细讲,戳链接回顾),但依然是悲观锁,线程开始执行第一步就要获取锁,一旦获得锁,其他的线程进入后就会阻塞并等待锁。

CAS 是乐观锁,线程执行的时候不会加锁,它会假设此时没有冲突,然后完成某项操作;如果因为冲突失败了就重试,直到成功为止。

12.1乐观锁与悲观锁

锁可以从不同的角度来分类。比如我们在前面讲 synchronized 四种锁状态的时候,提到过偏向锁、轻量级锁、重量级锁,对吧?乐观锁和悲观锁也是一种分类方式。

悲观锁

对于悲观锁来说,它总是认为每次访问共享资源时会发生冲突,所以必须对每次数据操作加上锁,以保证临界区的程序同一时间只能有一个线程在执行。

乐观锁

乐观锁,顾名思义,它是乐观派。乐观锁总是假设对共享资源的访问没有冲突,线程可以不停地执行,无需加锁也无需等待。一旦多个线程发生冲突,乐观锁通常使用一种称为 CAS 的技术来保证线程执行的安全性。

由于乐观锁假想操作中没有锁的存在,因此不太可能出现死锁的情况,换句话说,乐观锁天生免疫死锁。


乐观锁多用于“读多写少“的环境,避免频繁加锁影响性能;
悲观锁多用于”写多读少“的环境,避免频繁失败和重试影响性能。


12.2什么是 CAS

在 CAS 中,有这样三个值:

V:要更新的变量(var)
E:预期值(expected)
N:新值(new)

比较并交换的过程如下:

判断 V 是否等于 E,如果等于,将 V 的值设置为 N;如果不等,说明已经有其它线程更新了 V,于是当前线程放弃更新,什么都不做。

这里的预期值 E 本质上指的是“旧值”。

我们以一个简单的例子来解释这个过程:

如果有一个多个线程共享的变量i原本等于 5,我现在在线程 A 中,想把它设置为新的值 6;
我们使用 CAS 来做这个事情;
首先我们用 i 去与 5 对比,发现它等于 5,说明没有被其它线程改过,那我就把它设置为新的值 6,此次 CAS 成功,i的值被设置成了 6;
如果不等于 5,说明i被其它线程改过了(比如现在i的值为 2),那么我就什么也不做,此次 CAS 失败,i的值仍然为 2。
在这个例子中,i就是 V,5 就是 E,6 就是 N。

  • 那有没有可能我在判断了i为 5 之后,正准备更新它的新值的时候,被其它线程更改了i的值呢?

不会的。因为 CAS 是一种原子操作,它是一种系统原语,是一条 CPU 的原子指令,从 CPU 层面已经保证它的原子性。

当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。

12.3 CAS的缺点

(1)ABA问题

如果一个线程t1正修改共享变量的值A,但还没修改,此时另一个线程t2获取到CPU时间片,将共享变量的值A修改为B,然后又修改为A,此时线程t1检查发现共享变量的值没有发生变化,但是实际上却变化了。

  • 解决办法: 使用版本号,在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A-B-A 就会变成1A-2B-3A。从Java1.5开始JUC包里提供了一个类AtomicStampedReference来解决ABA问题。AtomicStampedReference类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前版本号是否等于预期版本号,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
(2)循环时间长开销会比较大:

自旋重试时间,会给CPU带来非常大的执行开销

(3)只能保证一个共享变量的原子操作,不能保证同时对多个变量的原子性操作
  • 解决办法:
    从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,
    你可以把多个变量放在一个对象里来进行CAS操作

12.4 CAS使用注意事项

  • (1)CAS需要和volatile配合使用

CAS只能保证变量的原子性,不能保证变量的内存可见性。CAS获取共享变量的值时,需要和volatile配合使用,来保证共享变量的可见性

  • (2)CAS适用于并发量不高、多核CPU的情况

CPU多核情况下可以同时执行,如果不合适就失败。而并发量过高,会导致自旋重试耗费大量的CPU资源

13.HashMap和HashTable区别 难度系数:⭐

线程安全性不同
HashMap是线程不安全的,HashTable是线程安全的,其中的方法是Synchronized,在多线程并发的情况下,可以直接使用HashTable,但是使用HashMap时必须自己增加同步处理。

是否提供contains方法
HashMap只有containsValue和containsKey方法;HashTable有contains、containsKey和containsValue三个方法,其中contains和containsValue方法功能相同。

key和value是否允许null值
Hashtable中,key和value都不允许出现null值。HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。

数组初始化和扩容机制
HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。

Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。
————————————————

                        leader_song

原文链接:https://blog.csdn.net/leader_song/article/details/132094080

  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值