互联网架构学习-第一章 并发编程基础

篇外:推荐使用的IDE是基于Eclipse开源工程的进行Spring Boot, Spring Cloud 开发的STS(Spring Tools Suite)。

1 第一章 并发编程基础

1.1 并发容器类

同步类容器都是线程安全的。但在某些情况下需要加锁来保护复合操作。

1.1.1 同步类容器的常见问题

下面两种写法(增强For循环迭代器While循环)对容器内容进行修改的时候会触发ConcurrentModificationException异常。

	// 增强For循环中,移除元素导致异常Exception发生:ConcurrentModificationException
	public Collection<String> m1(Vector<String> list){
		for ( String temp : list) {
			if("3".equals(temp)) {
				list.remove(temp);
			}
		}
		return list;
	}

	// 迭代器While循环中,移除元素导致异常Exception发生:ConcurrentModificationException
	public Collection<String> m2(Vector<String> list){
		Iterator<String> iterator = list.iterator();
		while ( iterator.hasNext() ) {
			String temp = iterator.next();
			if("3".equals(temp)) {
				list.remove(temp);
			}
		}
		return list;
	}

从下面的图上可以看到异常时怎么抛出的。
互联网架构学习-第一章 并发编程基础
使用普通For循环可以避免上述异常,

	// 普通For循环:Success
	public Collection<String> m3(Vector<String> list){
		for ( int i = 0; i<list.size(); i++ ) {
			if("3".equals(list.get(i))) {
				list.remove("3");
			}
		}
		return list;
	}
1.1.2 同步类容器的使用

同步类容器包括Vector, HashTable等。
这些容器的同步功能其实都是有JDK的Collections.synchronized等工厂方法去创建实现的。
其底层的机制无非就是用
synchronized
*关键字对每个公用的方法都进行同步,或者使用Object mutex对象锁的机制使得每次只能有一个线程访问容器的状态。
通过Collections.synchronizedCollection()方法,可以将普通的list变成为线程安全的list。

	List<String> list= new ArrayList<>();
	Collections.synchronizedCollection(list);
并发类容器概念
  • jdk5.0以后提供了多种并发类容器来替代同步类容器从而改善性能。

  • 同步类容器的状态都是串行化的。

  • synchronized关键字虽然实现了线程安全,但是严重降低了并发性,在多线程环境时,严重降低了应用程序的吞吐量。
    常见的并发类容器包括ConcurrentMap、CopyOnWrite***等。

  • ConcurrentMap接口下有俩个重要的实现:
    ConcurrentHashMap
    ConcurrentSkipListMap(支持并发排序功能)

  • ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的HashTable,它们有自己的锁。

  • 只要多个修改操作发生在不同的段上,它们就可以并发进行。把一个整体分成了16个段(Segment),也就是最高支持16个线程的并发修改操作。

  • 这也是在多线程场景时减小锁的粒度从而降低锁竞争的一种方案。并且代码中大多共享变量使用volatile关键字声明,目的是第一时间获取修改的内容,性能非常好。

Copy On Write
Copy On Write 简称COW,是一种用于程序设计中的优化策略,可以在非常多的并发场景中使用到。
JDK里的COW容器有两种:CopyOnWriteArrayList、CopyOnWriteArraySet

什么是Copy On Write, 通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。这种容器适合于读多写少而且容器不太大的场景。

1.1.3 并发队列

在并发队列上JDK提供了两套实现,一个是以ConcurrentLinkedQueue为代表的高性能队列,一个是以BlockingQueue接口为代表的阻塞队列,无论哪种都继承自Queue接口!

ConcurrentLinkedQueue
ConcurrentLinkedQueue是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueue。它是一个基于链接节点的无界线程安全队列。
ConcurrentLinkedQueue重要方法:

  • add() 和 offer() 都是加入元素的方法 (在ConcurrentLinkedQueue中,这俩个方法没有任何区别)
  • poll() 和 peek() 都是取头元素节点,区别在于前者会删除元素,后者不会。
    BlockingQueue
  • offer(anObject): 表示如果可能的话, 将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳, 则返回true, 否则返回false.(本方法不阻塞当前执行方法的线程)
  • offer(E o, long timeout, TimeUnit unit), 可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
  • put(anObject): 把anObject加到BlockingQueue里, 如果BlockQueue没有空间, 则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
  • poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
  • take(): 取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
  • drainTo(): 一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。

模拟自己的阻塞队列

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class MyQueue {

	// 整个队列的容器
	private final LinkedList<Object> list = new LinkedList<>();
	
	// 计数器
	private final AtomicInteger count = new AtomicInteger(0);
	
	private int maxSize = 0; //最大容量限制
	
	private final int minSize = 0; //最小容量限制
	
	private final Object lock = new Object(); //锁
	
	public MyQueue(int maxSize) {
		this.maxSize = maxSize;
	}
	
	public void put(Object obj) {
		// 如果容器已满,等待空余出现
		synchronized (lock) { //阻塞操作
			while(count.get() == maxSize) {
				try {
					lock.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			//添加新元素到容器里
			list.add(obj);
			count.getAndIncrement();
			System.err.println("元素:" + obj + " 已经添加到容器中"); 
			// 进行唤醒可能正在等待的take方法操作中的线程
			lock.notify();
		}
		
	}
	
	public Object take() {
		Object temp = null;
		synchronized (lock) { //阻塞操作
			while (count.get() == minSize) {
				try {
					lock.wait();
				}catch(InterruptedException e) {
					e.printStackTrace();
				}
			}
			temp = list.removeFirst();
			count.getAndDecrement(); // i--
			System.err.println("元素:" + temp + " 已经从容器移除"); 
			// 进行唤醒可能正在等待的take方法操作中的线程
			lock.notify();
		}
		return temp;
	}
	
	public int size() {
		return count.get();
	}
	
	public List<Object> getQueueList() {
		return list;
	}
}

1.2 线程安全核心概念

1.3 Volatile及原子性核心概念

1.4 J.U.C工具类

1.5 线程池核心

1.6 AQS

相关文章:

  • 2021-12-14
  • 2022-02-06
  • 2022-12-23
  • 2022-12-23
  • 2021-11-25
  • 2021-10-17
猜你喜欢
  • 2021-11-11
  • 2022-03-08
  • 2022-12-23
  • 2021-12-22
  • 2021-04-27
  • 2021-12-01
  • 2022-12-23
相关资源
相似解决方案