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

篇外:推荐使用的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;
	}
}

DelayQueue的使用
下面的例子讲的是在一个大型购物中心中设置的充气城堡的场景。 通过为时间付费,孩子们可以在相应的时间内在充气城堡中愉快滴玩耍^_^, 时间到了之后就会提醒孩子游玩结束,请父母来接孩子走。按照时间到达的顺序,对象依次离开队列。
下面是源代码。

import java.util.concurrent.DelayQueue;

public class BouncyCastle implements Runnable {

	private DelayQueue<Child> delayQueue = new DelayQueue<>();
	public boolean start = true;

	/**
	 * 在充气城堡游玩。 到时间后通知大人来接孩子。 本方法启动游玩。
	 * 
	 * @param name
	 * @param parentPhoneNumber
	 * @param money
	 */
	public void startPlaying(String name, String parentPhoneNumber, int money) {
		Child child = new Child(name, parentPhoneNumber, System.currentTimeMillis() + money * 2000);
		System.out.println("孩子姓名:" + name + ",家长电话:" + parentPhoneNumber + ",缴费: " + money + "元,现在开始游玩,可以玩"
				+ String.valueOf(money * 2) + "秒。");
		delayQueue.add(child);
	}

	/**
	 * 结束游玩
	 * 
	 * @param child
	 */
	public void stopPlaying(Child child) {
		System.out.println("时间到!请接走" + child.getName() + "小朋友,父母电话:" + child.getParentPhoneNumber());
	}

	public void run() {
		while (start) {
			try {
				Child child = delayQueue.take();
				stopPlaying(child);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) {
		System.out.println("充气城堡开始营业!");

		BouncyCastle bouncyCastle = new BouncyCastle();
		Thread yingye = new Thread(bouncyCastle);
		yingye.start();

		bouncyCastle.startPlaying("宸宸", "13912345678", 1);
		bouncyCastle.startPlaying("Elisa", "13811112222", 3);
		bouncyCastle.startPlaying("Augus", "17355556666", 2);
	}

}

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class Child implements Delayed{

	// 孩子名字
	private String name;
	// 孩子家长电话
	private String parentPhoneNumber;
	// 游乐结束时间
	private long endTime;
	
	private final TimeUnit timeUnit = TimeUnit.MILLISECONDS;

	/**
	 *	 
	 */
	public Child() {
	}

	/**
	 * @param name
	 * @param parentPhoneNumber
	 * @param endTime
	 */
	public Child(String name, String parentPhoneNumber, long endTime) {
		super();
		this.name = name;
		this.parentPhoneNumber = parentPhoneNumber;
		this.endTime = endTime;
	}


	@Override
	public int compareTo(Delayed o) {
		Child child = (Child)o;
		long diff = this.getDelay(timeUnit) - child.getDelay(timeUnit); 
		return diff == 0 ? 0 : (diff > 0 ? 1 : -1 );
	}

	@Override
	public long getDelay(TimeUnit unit) {
		return endTime - System.currentTimeMillis() ;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public void setEndTime(long endTime) {
		this.endTime = endTime;
	}
	
	public void setParentPhoneNumber(String parentPhoneNumber) {
		this.parentPhoneNumber = parentPhoneNumber;
	}
	
	public long getEndTime() {
		return endTime;
	}
	
	public String getName() {
		return name;
	}
	
	public String getParentPhoneNumber() {
		return parentPhoneNumber;
	}
}

这里是输出结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值