上篇中学习了多进程和多线程的概念,该篇总结下多线程编程中的一些概念和遇到的问题。
一,线程安全,同步
1,锁
1.1基本概念
锁的分类:偏向锁、轻量级锁、重量级锁
锁升级:根据线程竞争锁的激烈程度,锁会从偏向锁升级为重量级锁
1.2 jvm对锁的优化
jvm内置锁存在的基本问题:线程切换的成本大,主要表现为:内核态和用户态的切换,线程切换
为了换取性能,JVM在内置锁上做了非常多的优化,膨胀式的锁分配策略就是其一
1.3 什么是CAS操作
常被用来解决独占锁对线程阻塞而导致的性能低下问题,是高效并发必备的一种优化方法。
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试
CAS原理:基本原理是比较和替换:内存值 预期值 更新值, 一个进程在修改一个变量时,通过对比内存值和预期值,如果相等,才会将更新值替换到内存。底层使用了unsafe调用底层实现。
1.4 ReenTrantLock可重入锁(和synchronized的区别)总结
2.线程安全的类,如何设计一个线程安全的类
3.不使用同步关键字,如何实现一个线程安全的单例?
3.1
//不使用同步关键字实现一个线程安全的单例
public class Singleton {
//1.私有构造函数
private Singleton() {
}
//静态内部类,该模式使用了懒加载,类虽然加载了,但是实例没有被初始化
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
//公有的方法供外部使用
public static Singleton getInstance() {
return SingletonHolder.instance;
}
//总结:实现原理都是利用借助了类加载的时候初始化单例。即借助了ClassLoader的线程安全机制
}
3.2使用CAS机制
//不使用同步关键字实现一个线程安全的单例,使用CAS机制
public class CASSingleton {
//CAS变量
private static final AtomicReference<CASSingleton> INSTANCE = new AtomicReference<CASSingleton>();
//1.私有构造函数
private CASSingleton() {
}
public static CASSingleton getInstance() {
for (;;) {
CASSingleton casSingleton = INSTANCE.get();
if (null != casSingleton) {
return casSingleton;
}
casSingleton = new CASSingleton();
if (INSTANCE.compareAndSet(null, casSingleton)) {
return casSingleton;
}
}
}
}
用CAS的好处在于不需要使用传统的锁机制来保证线程安全,CAS是一种基于忙等待的算法,依赖底层硬件的实现,相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度。CAS的一个重要缺点在于如果忙等待一直执行不成功(一直在死循环中),会对CPU造成较大的执行开销。另外,如果N个线程同时执行到singleton = new Singleton();的时候,会有大量对象创建,很可能导致内存溢出。
4.容器
1.Reentrantlock
https://blog.csdn.net/u011521203/article/details/80186741
HashMap
1.hashmap的原理
2.hashmap三要素:hash 数组 链表
3.hashmap的长度,如何实现一个分布均匀的hash函数
index = HashCode(Key) & (hashMap.length - 1);
设计者使用 hashcode值与长度“与运算”,保证算出来的index位置均匀分布。因为(hashMap.length - 1)的二进制所有位数均为1
index的结果就等同于HashCode后几位的值。只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。这也是HashMap设计的玄妙之处。
4.HashMap扩容
5.HashMap冲突处理
6.HashMap的死锁
hashmap是非线程安全的,多线程下访问会造成死锁
https://coolshell.cn/articles/9606.html
总体介绍:参考:https://blog.csdn.net/mbshqqb/article/details/79799009
总结:
(1) 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。
(2) 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。
(3) HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap。
(4) JDK1.8引入红黑树大程度优化了HashMap的性能。
HashTable
1.线程安全的,但是效率低
一,线程安全的容器
Hashtable-->ConcurrentHashMap
Vector-->CopyOnWriteArrayList/CopyOnWriteArraySet
总结:新的线程安全容器比之前老的线程安全的容器相比,锁的粒度小了,安全访问的同时提高了访问效率
ConcurrentHashMap的原理
1.线程安全的实现
2.读取效率高
3.相比HashTable,锁的粒度变小,以一个Segment为单位,如果线程A读取SegmentA,此时线程B是无法访问SegmentA的,但是可以访问其它Segment段
二,线程池
https://cloud.tencent.com/developer/article/1006754?ref=myread