前言
在多线程环境下我们日常使用的很多类都存在线程安全问题,比如
ArrayList
、HashSet
、HashMap
,那么多线程环境下我们应该如何处理好线程问题?CAS算法是什么?除了synchronized
有没有别的方法实现线程安全?乐观锁?悲观锁?
同步容器
JUC
工具包中针对这些日常开发中经常使用的集合类,给我们提供了线程安全的类来代替这些类。
- ArrayList
-->
CopyOnWriteArrayList--
写复制列表- HashSet
-->
CopyOnWriteArraySet--
写复制集合- HashMap
-->
ConcurrentHashMap--
分段锁映射
CopyOnWriteArrayList
CopyOnWriteArrayList
底层通过操作“副本”的形式避免并多线程环境下得并发问题。
当一个线程对
list
进行操作时,会先加一把锁,然后将当前对象的值拷贝一份,重新创建一个长度+1
的新对象,然后将引用地址指向新的ArrayList
add()方法源码
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
CopyOnWriteArraySet
同
CopyOnWriteArrayList
类似,只是底层存储数据的数据结构不同
源码
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
ConcurrentHashMap
代码示例
public class ConcurrentHashMapSample {
public static int users = 100;//同时模拟的并发访问用户数量
public static int downTotal = 50000; //用户下载的真实总数
public static ConcurrentHashMap count = new ConcurrentHashMap() ;//计数器
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(users);
for(int i = 0 ; i < downTotal ; i++){
final Integer index = i;
executorService.execute(()->{
//通过多线程模拟N个用户并发访问并下载
try {
semaphore.acquire();
count.put(index, index);
semaphore.release();
} catch (Exception e) {
e.printStackTrace();
}
});
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();//关闭调度服务
System.out.println("下载总数:" + count.size());
}
}
ConcurrentHashMap
底层将原始数据切分成一个一个小的segment
,长度为2
的n
次方,然后分段加锁,多个线程同时处理,在保证安全的状态下也提升了一些效率。
扩展
原子性
原子性是指一个操作或多个操作要么全部执行,且执行的过程不会被任何因素打断,要么就都不执行。
Atomic包
Atomic
包是JUC
下得另一个专门为线程安全设计的Java
包,包含多个原子操作类Atomic
常用类
AtomicInteger
AtomicIntergerArray
AtomicBoolean
AtomicLong
AtomicLongArray
通过
synchronized
关键字保证下载量在多线程下的累加安全。这里可以通过更加轻量级的方法,利用Atomic
原子类完成优化。
优化代码
public static AtomicInteger count = new AtomicInteger() ;//计数器
...
//线程不安全
public static void add(){
count.getAndIncrement(); //count++
}
CAS算法
悲观锁
- 锁是用来做并发最简单的方式,当然其代价也是最高的。独占锁是一种悲观锁,
synchronized
就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
乐观锁
- 所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。其中CAS(比较与交换,
Compare And Swap
) 是一种有名的无锁算法。
Atomic的应用场景
虽然基于CAS的线程安全机制很好很高效,但要说的是,并非所有线程安全都可以用这样的方法来实现,这只适合一些粒度比较小型,如计数器这样的需求用起来才有效,否则也不会有锁的存在了。