jdk-并发包

918 阅读18分钟

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

作用 粒度更细