集合是java容器
在java中, 集合是java容器
这里的容器不是Spring中容器, 是一个封装了数据结构的容器
在计算机中容器的定义
是指统一打包了容器内的东西, 使之便于迁移
注重隔离性和可移植性
java容器与spring容器的区别
- java容器集合打包了各种数据结构
- 四种数据结构, list, queue, set, map
- Spring容器则打包了Bean
- 通过IoC(控制反转)和DI(依赖注入)的机制,将Bean的创建、配置和依赖关系管理起来。
- 为Bean提供了一个可移植的环境, 如从开发环境到生产环境, 只要spring容器相同, 其内部的Bean执行就一样
- 通过解耦和面向接口编程
集合底层结构
集合实现了两个接口, collection接口和map接口
collection接口实现了三个接口, list, set, queue接口
就这样把集合与数据结构关联起来
集合数据结构区别
- list列表: 有序, 可重复
- Set: 元素不可重复, 无序
- Queue: 数据先进先出, 有序可重复
- Map: 是一个键值对
List
arraylist与linkedlist的区别
这是两种集合, 一个使用数组实现, 一个使用双向链表实现
就是Java中顺序表与链表的实现
linkedlist实现了 list和 deque两个接口!
注意两个list都是线程不安全的
在执行多线程时要解决这个问题
解决Arraylist线程不安全
核心解决方法就是加锁
使用Collections.synchronizedList方法:
Collections.synchronizedList方法返回一个由指定列表支持的同步(线程安全的)列表。对返回的列表的所有访问都必须通过返回的列表进行。这样可以确保在多线程环境下对列表的访问是安全的。
使用CopyOnWriteArrayList:
CopyOnWriteArrayList是java.util.concurrent包下的一个线程安全的列表实现。它是通过在修改操作(如add, set等)时复制底层数组来实现线程安全的。因此,读取操作是无锁的,并且非常快。然而,写入操作(包括修改操作)可能涉及复制整个底层数组,这在数据量很大时可能会很昂贵。
使用关键字synchronized:
使用Java的内置锁(synchronized关键字)或java.util.concurrent.locks包下的锁来手动同步对ArrayList的访问。
Arraylist和Array的区别
Arraylist是动态数组
Arraylist中存储对象 [ 基本数据类型转换为包装类 ]
Queue
接口
有两个接口, Queue接口和deque
一个是简单队列, 一个是双端队列
ArrayQueue和linkedlist区别
ArrayQueue底层和linkedlist一样都实现了deque接口
但是ArrayDeque 是基于可变长的数组和双指针来实现
阻塞队列
除了阻塞队列, 其他队列都线程不安全
阻塞队列如何保证线程安全
队列为空时获取队列阻塞
队列为满时添加元素阻塞
获取队列元素时采用 先进先出的特性, 保证一次只有一个线程获取队列元素
多个线程依次获取数据时, 如果队列中数据被获取完了, 就会阻塞直到队列内存在元素
阻塞队列类型
ArrayBlockingQueue 和 LinkedBlockingQueue
- ArrayBlockingQueue添加和删除操作同一把锁
- LinkedBlockingQueue添加和删除不同锁
MAP
Hashmap
基于哈希表的map, 采用 key-value
- 线程不安全
- key使用哈希值进行存储
- key不安全
- 可以为null
- value采用链表或者红黑树进行存储
- 当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)
hashmap底层实现
Jdk1.8以前使用数组和链表实现
Jdk1.8后采用数组链表红黑树实现
特点
- key不重复
- 可为null
- 线程不安全
转换机制
- 哈希冲突时转换为链表存储具有相同哈希值的键
- 当该链表长度过长时[ >8 ], 把链表转换为红黑树
- 当红黑树中元素 <6 时, 红黑树退回链表
扩容机制
- 把hashmap存储value的总数组元素称为"桶"
- 当"桶"数量不够时, 触发扩容, 把数组扩容一倍 [ 16 --> 32 ]
- 在扩容后, 原本"桶"中的链表和红黑树会重新分配
- 扩容后value对应的key不会发生改变, 但是在数组中的存储位置会发生改变
如何理解?
桶的索引不是直接基于hash值, 而是基于桶数量取模
计算key的hash值,然后进行二次hash,根据二次hash结果找到对应的桶的索引位置
[ 注意这两次的hash不一样, 算法不同结果不同, 二次hash只是简单的一些处理 ]
如何实现线程安全
- 使用ConcurrentHashMap
- 使用HashTable
- Collections.synchronizedHashMap()方法
hashtable与hashmap区别
- hashtable线程安全 [ 每个方法都通过synchronized加锁, 读写慢 ]
- 基本被淘汰
- 不允许null作为key
- 底层仅仅是数组+链表
Currenthashmap
- 支持高并发, 线程安全的hashmap
- 就是在原本的hashmap基础上增加了锁来实现并发, 线程安全
- 也有扩容和红黑树转换
Synchrpnized 加锁
是什么
来实现锁的功能的,它可以确保同一时间只有一个线程可以执行特定的代码块或方法。
可以修饰对象, 方法和代码块
通过对子线程使用的方法, 类加SynchronizedUsed, 实现线程安全
就是防止两个子线程同时刻执行一个方法导致数据混乱
作用: 在同步线程中, 防止同时执行!
用该关键字修饰的方法, 对象, 在同一时刻只能有一个线程进入那个方法, 一个线程访问对象
例子:
package org.example.eightWindow;
/**
* 使用Synchronized 关键字的例子
*/
public class SynchronizedUsed {
private double balance;
// 构造器
public SynchronizedUsed(double initialBalance) {
this.balance = initialBalance;
}
// 同步方法:存款
// 是子线程会调用的方法, 使用关键字确保同时只会执行一个该方法
public synchronized void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println(Thread.currentThread().getName() + "存款 " + amount + ",当前余额:" + balance);
} else {
System.out.println("存款金额必须为正数");
}
}
// 获取余额
public double getBalance() {
return balance;
}
// 主方法,用于测试
public static void main(String[] args) {
SynchronizedUsed account = new SynchronizedUsed(1000);
// 创建两个线程分别进行存款操作
Thread thread1 = new Thread(() -> {
account.deposit(500);
}, "线程1");
Thread thread2 = new Thread(() -> {
account.deposit(300);
}, "线程2");
// 启动线程
thread1.start();
thread2.start();
}
}
Synchrpnized和lock的区别
- synchronized是关键字,lock是一个类
- synchronized在发生异常时会自动释放锁,lock需要手动释放锁
- synchronized是可重入锁、非公平锁、不可中断锁,lock的ReentrantLock是可重入锁,可中断锁,可以是公平锁也可以是非公平锁
- synchronized是JVM层次通过监视器实现的,Lock是通过AQS实现的