jdk为什么要发明并发包/类?
解决了隐式锁synchronized同步的性能问题。
并发包,是显式锁。
显式锁
try()和trylock的区别?
按阻塞和非阻塞划分
1.try
阻塞锁,就是获取不到的话,一直阻塞在那里,直到获取到锁为止。
2.trylock
非阻塞锁,就是只获取一次,无论是否获取到锁,都直接返回。
先来看下代码。
//jdk7-CopyOnWriteArrayList
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#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(); //释放锁
}
}
//jdk7-ConcurrentHashMap
/**
* 写节点数据到节点数组(段)
* <pre>
* @author gzh
* @date 2019年6月26日 上午9:40:45
* @param key
* @param hash
* @param value
* @param onlyIfAbsent
* @return
* </pre>
*/
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value); //获取锁
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node); //写数据
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock(); //释放锁
}
return oldValue;
}
为什么list是使用阻塞,而map使用非阻塞?或者说,什么时候应该使用阻塞,什么时候应该使用非阻塞?
待补充。
按公平锁和非公平锁划分
1.try
2.trylock
这两个方法都是非公平的,因为决定是否是公平锁是由构造器决定的。这两个方法只是在方法里面调用默认非公平锁的获取锁方法而已。如果想要使用公平锁,那么使用另外一个构造器指定使用公平锁即可。
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync(); //默认非公平锁
}
锁的阻塞和非阻塞
阻塞,本质上是等待,让当前线程等待。
1.阻塞锁
就是一直循环获取锁,直到别的线程是否锁,然后该线程获取到锁为止。
2.非阻塞锁
只获取一次,立即返回。
锁的公平和非公平
公平就是排队,是按顺序来的;不公平就是靠竞争,随机的。
1.公平锁
线程排队队列
2.非公平锁 //jdk显式锁,默认使用非公平锁
竞争
类
并发List
写时复制ArrayList-CopyOnWriteArrayList。
//CopyOnWriteArrayList-jdk8
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#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(); //释放锁
}
}
总结
虽然名字叫写时复制,但是其实就是使用了显式锁而已。
有没有LinkedList?为什么没有?
并发Set
和并发List一样,使用了显式锁。
怎么保证数据不重复?
1.非并发Set
TreeSet、HashSet,都是使用map来确保数据不重复。具体怎么确保?就是插入的时候,key的hash值一样,就不插入了。
2.并发Set
没有使用hash,而是遍历比较,值一样,就不插入了。见下面源码。
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot); //不存在,才插入数据
}
private static int indexOf(Object o, Object[] elements,
int index, int fence) {
if (o == null) {
for (int i = index; i < fence; i++)
if (elements[i] == null)
return i;
} else {
for (int i = index; i < fence; i++) //遍历比较数据
if (o.equals(elements[i]))
return i;
}
return -1;
}
并发Map
并发map
即类ConcurrentHashMap。
锁实现原理
jdk7
1.映射包含段Segment数组
2.每个段Segment包含了映射数据键值对数组
这样的优点是,每个Segment都有锁,锁的粒度更细,所以并发性能更高。
//源码-jdk7-ConcurrentHashMap
public V put(K key, V value) {
Segment<K,V> s; //定义段数据
if (value == null)
throw new NullPointerException();
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j); //写数据到段数组
return s.put(key, hash, value, false); //写数据到段
}
@SuppressWarnings("unchecked")
private Segment<K,V> ensureSegment(int k) {
final Segment<K,V>[] ss = this.segments;
long u = (k << SSHIFT) + SBASE; // raw offset
Segment<K,V> seg;
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
Segment<K,V> proto = ss[0]; // use segment 0 as prototype
int cap = proto.table.length;
float lf = proto.loadFactor;
int threshold = (int)(cap * lf);
HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) { // recheck
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s)) //插入Segment到Segment数组
break;
}
}
}
return seg;
}
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry<K,V> node = tryLock() ? null : //获取锁(非公平锁。一般都是非公平锁,非公平锁就是靠线程自己竞争锁,而不是按顺序来https://www.jianshu.com/p/d86faec4baa1)
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node); //插入键值对数据到键值对数组Segment
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock(); //是否锁
}
return oldValue;
}
注:jdk8,不是使用分段segment锁。
分段具体是怎么分的?几个数据一个段?
工作使用
统计支付通道支付成功次数
说明:有时候需要统计支付通道成功次数,目的是计算支付成功率。
格式:<channelId //通道id , count //支付成功次数,每次支付成功加1>
//代码-支付网关服务BankGateway
public class SuccessedCalculateService {
private static ConcurrentHashMap<String, Integer> successedMap = new ConcurrentHashMap<String, Integer>(); //定义数据,map也是使用同步map
public static void addSuccessedRecord(String channelId) {
try {
log.info(channelId + " added to map");
synchronized (channelId) { //给通道id上锁
Integer count = successedMap.get(channelId); //读数据
if (null == count) {
count = new Integer(0);
}
count = count + 1; //支付成功次数加1
successedMap.put(channelId, count); //写数据
}
} catch (Exception e) {
log.error(e);
}
}
总结
不光光是这一个支付网关服务需要统计支付通道支付成功的次数,以便计算支付成功率。其他所有的支付网关,都需要计算这个数据。也就是说,只要有这个需求的,都可以使用并发map这种数据结构来存储数据,具体计算的位置是在支付网关服务里。
ConcurrentHashMap-jdk7和jdk8底层实现的区别?
1.jdk7
1)数组 + 链表 //和HashMap一样
2)分段数组 //并发:1.可重入锁 2.每个段一个锁
/**
* The segments, each of which is a specialized hash table.
*/
final Segment<K,V>[] segments;
2.jdk8
1)数组 + 链表 //同上
2)synchronized + CAS //syncronized粒度更细
3)红黑树 //方便快速查找数据
并发包的作用
1.简单同步
下面2种情况,可以通过简单同步解决。
1)1个操作,包含多个指令
解决的是一个操作,但是这个操作包含了多条计算机指令。
这些指令执行时间不确定,导致多个线程会出现问题。
2)多个操作,但是都在1个方法里
2.并发包
还有一种情况,就是有的代码,包含了多个同步方法。
虽然这些方法每个都是同步的,但是可能在执行1个方法的时候改了数据(比如,数组删除一个元素,数组大小10改为9),另外一个方法读了数据,直接导致数组越界(数组元素个数还是以前的10)。
并发包就是要解决这个问题。
具体怎么解决?实现原理和底层实现?
并发队列ConcurrentLinkedQueue
底层实现
1.普通队列
数组
链表
2.并发包 只有链表实现ConcurrentLinkedQueue。为什么没有数组实现ConcurrentArrayQueue?
为什么没有数组实现ConcurrentArrayQueue?
普通队列的实现原理和底层实现?
1.数组
1)数组
存放数据。
2)数组索引指针,不是真的索引
用于指向队列的头和尾。
2.链表
节点类,包含
1)数据
2)对象指针
指向下一个节点。
并发包-链表队列的底层实现和实现原理?
应用场景
适合于,多线程访问同一个集合里的数据。
工作使用
定时器扫订单集合-ConcurrentLinkeQueue,来一个扫/消费一个。因为定时器是多线程,都消费这个集合里的数据。
是否有界/容量大小
1.数组 //必须指定大小
2.链表 //无界
阻塞队列LinkedBlockingQueue
数据结构
顾名思义,链表。
底层实现
阻塞的原理就是Object的等待和唤醒。
什么是阻塞队列?
1.队列
先进先出。
2.阻塞
一直卡在那里。
3.阻塞队列
有个线程,一直读阻塞队列里的数据,
1)来一个数据,就读一个数据
2)不来,就阻塞在那里,等待下一个订单数据的到来
上面是消费者的角度看。
如果从生产者的角度看,
1)一直往队列里写数据
2)没写满,就一直写;
写满了,就阻塞,直到有数据被消费,腾出了一个新的空间
注:非阻塞队列,即普通队列Queue,
1.没有数据,就返回null。
2.数据满了,就插不进去了。
什么是生产者-消费者模式?
只是一个设计模式而已。具体包括以下几个部分,
1.数据从哪里读?
有个定时任务去扫库,作用是读数据。
读数据的代码,就叫做生产者。
2.数据放在哪里?
阻塞队列。
3.数据写到哪里?
写数据的代码,就叫做消费者。
阻塞队列底层原理?怎么才能实现阻塞?
1.阻塞当前线程,比如生产者
如果集合已满,当前数据就不添加到集合,并且当前线程(即生产者)的代码就阻止在这里了。直到被其他线程(即消费者线程)唤醒,才继续接着执行接下来的代码,具体代码就是添加数据到集合,因为现在集合不是满的,刚刚被消费者线程消费了一个数据。
//jdk7-ArrayBlockingQueue
/**
* Inserts the specified element at the tail of this queue, waiting
* for space to become available if the queue is full.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) //如果集合已满,那么当前线程(即生产者线程)等待
notFull.await();
insert(e); //直到消费者线程消费了一个数据,并且唤醒生产者线程,才接着执行这行代码,即插入数据。 //注:另外一个需要注意的地方是,只要是让锁持有的线程等待的话,它都是循环调用,而不是只调用一次。jdk7-Thread.join()方法也是循环调用wait,让锁持有线程(即父线程)等待。
} finally {
lock.unlock();
}
}
/**
* Inserts element at current put position, advances, and signals.
* Call only when holding lock.
*/
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal(); //生产者线程生产一个数据之后,唤醒消费者线程,如果确实有消费者线程正在等待,那么消费者线程被唤醒之后继续工作即消费数据。
}
2.阻塞当前线程,比如消费者
同理。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) //消费者线程发现集合里没有数据,就一直等等
notEmpty.await();
return extract(); //直到生产者线程插入数据,并且唤醒了消费者线程
} finally {
lock.unlock();
}
}
/**
* Extracts element at current take position, advances, and signals.
* Call only when holding lock.
*/
private E extract() {
final Object[] items = this.items;
E x = this.<E>cast(items[takeIndex]);
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal(); //消费者线程消费一个数据之后,唤醒生产者线程,如果确实有生产者线程正在等待,那么生产者线程被唤醒之后继续工作即插入数据到集合。
return x;
}
总结 所以阻塞是怎么实现的?就是通过wait/notify机制实现的!
参考 blog.csdn.net/u010412719/… webcache.googleusercontent.com/search?q=ca…
Condition?
作用
和Object.wait/notify一样,
1.让当前线程等待
2.唤醒其他等待线程
那与Object.wait/notify的区别?
优点,可以定向通知。而Object要么随机通知要么通知所有。
注:显式锁/可重入锁RetrantLock,有可以控制锁的粒度,等等优点。
怎么使用?
Object
1.锁对象 //普通的对象
2.线程调用锁对象的wait/notify,从而让当前线程等待,或唤醒其他线程
Condition
1.锁对象 //可重入锁RetrantLock
Condition必须与RetrantLock结合使用,是一对。
2.线程调用Condition.wait/notify,从而让当前线程等待,或唤醒其他线程
具体是在显式锁之间的代码块里调用Condition.wait/notify。例子,见jdk7-ArrayBlockingQueue.put()。
使用哪一种阻塞队列?
有2种,
1.数组
2.链表
JDK线程池ThreadPoolExecutor使用的是LinkedBlockingQueue,用来放任务队列。为什么要用链表队列?因为线程池大小不固定。哪怕一开始设置了线程池大小,后面也可以改变大小。数组的优点是读数据快,但是大小固定,插入/删除慢,而链表的优缺点刚好相反,所以当实际应用场景选择哪一种数据结构更好的时候,只需要根据优缺点以及应用场景需要哪一种优缺点,就可以决定使用哪一种数据结构了。
二者的关系?
应用场景
1.支付系统-订单 并发链表队列 ConcurrentLinkedQueue
2.订单 //以下可能用到并发map ConcurrentHashMap 3.通道 4.订单和通道
2.服务器客户端程序
服务器是消费者,处理请求。
客户端是生产者,生产请求。
3.其他的任何处理数据和生产数据,都可以看作是生产者-消费者模式
工作使用
实名认证服务。
1.支付系统
订单的生产者-消费者。
使用的是哪一个实现类?数组还是链表?
因为不需要从中间插入和删除数据,所以是数组。
上面说的1的这种情况是错误的,支付系统不是使用阻塞队列,消费者不是一直等待处理数据,服务器客户端程序的服务器才是使用的阻塞队列,但是支付系统的订单不是,因为订单是使用定时任务去扫描生产者放入队列的数据,使用的队列只是普通队列,而不是阻塞队列,没必要阻塞,也不是阻塞的应用场景。
具体是使用的并发链表队列ConcurrentLinkedQueue。
并发包为什么没有并发数组队列?但是阻塞队列是有数组和链表2种实现。
数组阻塞队列和链表阻塞队列的区别?
1.数组
读数据。
2.链表
从中间插入和删除。
阻塞队列与生产者-消费者设计模式的关系?
是否有界/容量大小
1.ArrayBlockingQueue //必须指定大小
2.LinkedBlockingQueue //有界
1)默认:Interger.最大值 //因为值很多,相当于是无界
2)支付-订单:1000
3.优先级 //无界
并发队列-何时使用非阻塞?何时使用阻塞?
应用场景
一个集合数据,先进先出。
何时使用非阻塞?
工作使用?
支付-订单 //非阻塞
要点和步骤?
具体是怎么生产数据的?定时任务里,调用远程服务-消费者服务的服务.写数据()。
数据保存在哪里?根据上面一点,可以知道,数据是保存在消费者服务.并发队列。
代码
//生产者服务:生产数据
/**
* 3 分钟执行一次,查询前2分钟到前10分钟内的订单
*/
@SuppressWarnings("unchecked")
public void checkUnpayOrder() {
try {
log.info("run checkUnpayOrder task start......");
Map<String, Object> params = new HashMap<String, Object>();
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, -2);
String endDateStr = DateFormatUtils.format(calendar, DATE_FORMAT);
params.put("endDate", endDateStr);
calendar.add(Calendar.MINUTE, -8);
String startDateStr = DateFormatUtils.format(calendar, DATE_FORMAT);
params.put("startDate", startDateStr);
// params.put("endDate", "2017-04-12 00:00:00");
//
// params.put("startDate", "2017-04-09 00:00:00");
List<Order> list = (List<Order>) orderDao.find("getUnpayOrders", params);
if (null == list || list.size() == 0) {
log.info("no unpay order found!");
return;
}
log.info("unpay orders has found! size is " + list.size());
for (Order order : list) {
log.info("produce a unpayOrder" + order.getOrderId());
checkBankOrderService.checkOrderResult(order.getOrderId(), order); //生产者往里面写数据
}
log.info("run checkUnpayOrder task end......");
} catch (Throwable e) {
log.error("run checkUnpayOrder task error!", e);
}
}
//消费者服务:消费数据
/**
* 轮询所有对队列,一旦有内容,则调用队列消费者对其进行消费
*/
@Override
public void run() {
try {
Map<IConsumerService, ConcurrentLinkedQueue<?>> consumerServiceMap = ConsumerServiceFactory.getConsumerServiceMap();
Set<IConsumerService> consumerServiceSet = consumerServiceMap.keySet();
for (IConsumerService consumerService : consumerServiceSet) {
ConcurrentLinkedQueue<?> queue = (ConcurrentLinkedQueue<?>) consumerServiceMap.get(consumerService); //非阻塞
while(!queue.isEmpty()){ //实际上是自己实现阻塞 //定时任务:循环消费 //每个线程消费一定数量的数据
log.info(consumerService.getClass().getName()+" received a element to consume.");
Object queueObj = queue.poll();
consumerService.consume(queueObj);
log.info(consumerService.getClass().getName()+" consumed a element.");
}
}
} catch (Exception e) {
log.error(e);
}
}
何时使用阻塞?
支付-实名认证 //阻塞 队里里的数据?线程。
阻塞队列数据,在哪里何时被消费?
何时结束?一直卡着?当数据全部消费完毕,线程结束的时候。
架构图

代码
package com.gzh.dpp.identity.tss.job;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.gzh.dpp.domain.member.verify.AccountVerify;
import com.gzh.dpp.identity.tss.service.AccountAuthCheckService;
/**
* @ClassName: IdCheckReulstJob
* @Description: 身份核查定时任务
*/
public class CAUnkownResultCheckJob {
private static Log log = LogFactory.getLog(CAUnkownResultCheckJob.class);
private AccountAuthCheckService accountAuthCheckService;
/** 10个线程检查异步订单,当队列满了之后,等待 */
protected static Executor executor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1000),
new RejectedExecutionHandler() { //队列已满,阻塞等待直到队列空闲
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (!executor.isShutdown()) {
try {
executor.getQueue().put(r); //写数据:如果队列数据已满,那么调用put()等待空余位置再写入
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
}
}
});
public void execute() {
log.info("CAUnkownResultCheckJob start...");
try {
List<AccountVerify> list = accountAuthCheckService.getNeedCheckRecords();
for (final AccountVerify record : list) {
try {
executor.execute(new Runnable() {
@Override
public void run() {
try {
accountAuthCheckService.checkAccountResult(record.getPid(), record.getChannelCode(), record.getBankSerialno());
} catch (Exception e) {
log.error("CAUnkownResultCheckJob error:", e);
}
}
});
} catch (Exception e) {
log.error("CAUnkownResultCheckJob submit task error:" + e);
}
}
} catch (Exception e) {
log.error("CAUnkownResultCheckJob excute cause an exception:" + e);
}
}
public AccountAuthCheckService getAccountAuthCheckService() {
return accountAuthCheckService;
}
public void setAccountAuthCheckService(AccountAuthCheckService accountAuthCheckService) {
this.accountAuthCheckService = accountAuthCheckService;
}
}
总结
应用场景
1.订单
数据量大
分离数据的生产和消费。//思想
需要自己实现阻塞。怎么实现?while(队列有数据){}
数据流
1)生产者
数据库——》定时任务。 //保存到队列
2)消费者
定时任务:消费队列数据——》通道指令服务。
2.实名认证 使用jdk阻塞-LinkedBlockingQueue。
定时任务定时时间?几秒钟。实名认证要快。
3.转账
数据量小 //相对于订单而言
使用定时任务直接扫库。没有分离数据的生产和消费,都是在同一个服务处理。
数据流
数据库——》定时任务——》通道指令服务。
生产者和消费者模式,有2种实现:
1.非阻塞队列ConcurrentLinkedQueue + while()
订单,数据量大。
2.jdk阻塞队列LinkedBlockingQueue 实名认证,数据量不大。
生产者和消费者的速度
1.生产者不能过快
2.消费者不能过慢
否则,队列集合越来越大,占满内存。
线程池
并发包-原子赋值
是什么 AtomicInteger。
作用 原子赋值。
1.普通赋值操作
是由多条指令完成的,需要读值,然后又要写值,等等。
2.原子赋值
操作系统级别支持这一的原子指令。
底层实现 1.compareAndSwap()
2.jvm支持这样的指令
compareAndSwap()和compareAndSet()的区别
代码
应用场景
参考
总结
一、同步集合 集合 1.使用显式锁,粒度更细
jdk源码-CopyOnWriteArrayList
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();
}
}
为什么更快?
1.读更快,因为读没有同步,可以多个线程同时读数据。
2.写使用显示锁进行同步,速度和隐式锁一样。
那为什么还要这么弄呢?因为CopyOnWrite的应用场景是读多写少,在实际应用场景当中也有很多这种需求——允许读的数据有少量误差,对数据的实时性要求没有那么高。
3.那数据会不会错误?会。但是旧的同步集合和新的并发集合有一点区别,
1)旧的同步集合
并发读和写,会报错-并发读写错误。
2)新的并发集合
每次当写数据的时候,数据复制了一份出来,写的时候是往新的数组里写,读线程读的数据是旧的数组,所以两者互不影响,代码也不会报错-并发读写错误。
www.cnblogs.com/chengxiao/p…
juejin.cn/post/684490…
应用场景? 工作中用的比较少。
映射 底层实现 1.Segment每个都有锁对象 2.每个版本的jdk实现都有很大的改变
jdk源码-ConcurrentHashMap
//怎么插入数据?比较和交换CAS
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
二、线程池
应用场景 定时器扫库
三、显式锁 ReentrantLock
作用 粒度更细