目录
- 概念说明
- 数据结构
- 线程安全
- HashMap示例
- 运行结果
- ConcurrentHashMap示例
- 运行结果
- 涉及技术
- Synchronized
- 概念
- 特性
- CAS(Compare And Swap)
- 概念
- 原理
- 代码演示
- 没有使用CAS的代码
- 运行结果
- 使用CAS的代码
- 运行结果
- 总结提升
概念说明
ConcurrentHashMap是Java中的线程安全的哈希表实现,它允许多个线程同时读取和写入数据,并且支持高并发访问。下面是ConcurrentHashMap、HashMap和HashTable的区别的二维表:
数据结构
ConcurrentHashMap和HashMap的数据结构是一样,由数组+链表+红黑树组成的。当向数组中出入的元素的hashcode都一样的情况下会形成链表结果,由于链表的时间复杂度是O(n),当链表过长的时候就会导致查询数据比较慢。所以当数组的长度为8的时候,链表结果就会转换成红黑树的结构,红黑树的时间复杂度是O(nlong)来提高查询数据的效率。以下是Map中涉及到的参数说明:
// 数组容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认长度
private static final int DEFAULT_CAPACITY = 16;
// 链表树化条件-是根据线程竞争情况和红黑树的操作成本进行设计的。
static final int TREEIFY_THRESHOLD = 8;
// 取消树化条件-为了避免过度的树化,防止内存占用过高。
static final int UNTREEIFY_THRESHOLD = 6; //链表结构中,每个节点只需要存储指向下一个节点的指针,而不需要存储节点的值。因此,链表只需要存储节点的引用,占用较少的内存空间。树结构中每个节点需要存储节点的值以及指向子节点的指针。
线程安全
通过使用HashMap和ConcurrentHashMap来对比一下,当在高并发的情况下是否会发生线程安全的问题
HashMap示例
public class HashMapUnsafeTest {
public static void main(String[] args) throws InterruptedException {
//演示HashMap
Map<String, String> map = new HashMap<>();
for (int i = 0; i < 30; i++) {
String key = String.valueOf(i);
new Thread(() -> {
//向集合添加内容
map.put(key, UUID.randomUUID().toString().substring(0, 8));
//从集合中获取内容
System.out.println(map);
}, "").start();
}
}
}
运行结果
ConcurrentHashMap示例
public class ConcurrentHashMapSafe {
public static void main(String[] args) throws InterruptedException {
//演示ConcurrentHashMap
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
String key = String.valueOf(i);
new Thread(() -> {
//向集合添加内容
map.put(key, UUID.randomUUID().toString().substring(0, 8));
//从集合中获取内容
System.out.println(map);
}, "").start();
}
}
}
运行结果
涉及技术
Synchronized
概念
synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。即synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized 翻译为中文的意思是同步,也称之为”同步锁“。它包括两种用法:synchronized 方法和 synchronized 块。
特性
「可见性 」:是指一个线程对共享变量进行修改,另一个线程立即得到修改后的新值。
「原子性 」:在一次或多次操作中,要么所有的操作都执行并且不会受其他因素干扰而中断,要么所有的操作都不执行。
「有序性 」:程序执行的顺序按照代码的先后顺序执行。编译器为了优化性能,有时候会改变程序中语句的先后顺序。
在使用多线程进行并发编程的时候,如果有多个线程来操作共享数据,很有可能共享数据的值会出现错乱,我们称之为线程安全问题。导致出现问题的原因有:可见性问题;原子性问题;有序性问题。这时候我们就可以通过使用synchronized 关键字来解决出现的问题。
CAS(Compare And Swap)
概念
在CAS中,有这样三个值:
V:要更新的变量(var)
E:预期值(expected)
N:新值(new)
比较并交换的过程如下:
判断V是否等于E,如果等于,将V的值设置为N;如果不等,说明已经有其它线程更新了V,则当前线程放弃更新,什么都不做。
原理
unsafe类——以下是类中涉及到的三个方法用来实现CAS效果的,这三个方法都是由native进行修饰的。具体的实现是由C++写的。
三个方法传入的参数都是一样的,只不过根据传入的类型不同选择不同的方法,第一个参数是当前这个对象,第二个参数线程之间共享的变量,第三个参数是预期值,第四个参数想要修改的值。
代码演示
没有使用CAS的代码
/**
* @BelongsProject: demo
* @BelongsPackage: com.example.threadpool.CAS
* @Author: Wuzilong
* @Description: 没有使用CAS的实例
* @CreateTime: 2023-07-29 09:44
* @Version: 1.0
*/
public class NoCASDemo {
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
//线程一
Thread thread1= new Thread(() -> {
for (int i=0; i<10000;i++){
counter++;
}
});
//线程二
Thread thread2= new Thread(() -> {
for (int i=0; i<10000;i++){
counter++;
}
});
//执行线程
thread1.start();
thread2.start();
//等待执行完线程1和2
thread1.join();
thread2.join();
System.out.println("查看counter的总数"+counter);
}
}
运行结果
多次运行程序会发现counter的总数是不一样的,说明有的线程操作的是同一个数值,导致两次i++的结果是一样的。
使用CAS的代码
/**
* @BelongsProject: demo
* @BelongsPackage: com.example.threadpool.CAS
* @Author: Wuzilong
* @Description: 使用CAS的实例
* @CreateTime: 2023-07-29 09:36
* @Version: 1.0
*/
public class CASDemo {
private static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
//线程一
Thread thread1= new Thread(() -> {
for (int i=0; i<10000;i++){
increment();
}
});
//线程二
Thread thread2= new Thread(() -> {
for (int i=0; i<10000;i++){
increment();
}
});
//执行线程
thread1.start();
thread2.start();
//等待执行完线程1和2
thread1.join();
thread2.join();
System.out.println("查看counter的总数"+counter.get());
}
public static void increment() {
int currentValue;
int newValue;
do {
//获取counter对象的value值
currentValue = counter.get();
//将counter对象的value值加1
newValue = currentValue + 1;
} while (!counter.compareAndSet(currentValue, newValue));
}
}
运行结果
多次运行程序发现counter的总数都是20000,这就说明使用了CAS之后不会出现线程安全的问题,当共享变量与预期值不一致的时候就取消了当前线程的操作。
总结提升
ConcurrentHashMap是一个线程安全的哈希表实现,它通过锁分段技术实现了高效的并发性能,支持高效的并发更新和弱一致性的迭代器。但需要注意的是,ConcurrentHashMap不支持存储null键和null值。在多线程环境下,使用ConcurrentHashMap可以提高并发性能,并且无需额外的同步措施。