1 同步类容器
同步类容器都是线程安全的,但是在某些场景可能需要加锁来保护复合操作。复合类操作如:迭代(反复访问元素,遍历容器中的所有元素)、跳转
根据指定的顺序找到当前元素的下一个元素)、以及条件运算。这些操作在多线程并发的修改容器时,可能会表现出意外的行为,最经典的就是
ConcurrentModificationException,原因是当容器迭代过程中被并发的修改了内容,这是由于早期的迭代器设计的时候并没有考虑到并发修改的
问题。
同步类容器:如早期的Vector、HashTable。这些容器的同步功能都有jdk的Collections.synchronized等工厂方法创建实现的。底层的机制无非就
是利用传统的synchronzed关键字对每个公用的方法都进行同步,使得每次只能有一个线程访问容器的状态,这很明显不满足我们今天互联网时代高
并发的需求,在保证线程安全的同时也要保证性能。
Collections.synchronizedList或synchronizedMap方法实际上就是对list和map进行wapper(包装),进行同步操作,保证线程安全,类似于hashTable
原理就是原本的基础上加了好多同步代码块。
2 并发类容器
先说一下同步类容器产生的问题,同步类容器执行都是串行化的,他们虽然实现了线程安全,但是严重降低了并发性,在多线程的环境中降低了应用的
吞吐量。
并发类容器:使用ConcurrentHashMap来代替散列的传统的HashTable,而且在ConcurrentHashMap中,添加了一些常用的复合操作的支持。使用
CopyOnWriteArrayList代替Voctor,并发的CopyOnWriteArraySet和并发的Queue,例如高性能的队列,ConcurrentLinkedQueue和阻塞形式的队列
LinkedBlockingQueue,其他类型的队列还有ArrayBlockingQueue、PriorityBlockingQueue和SynchronousQueue等。
ConcurrentMap接口下有俩个重要实现类:
ConcurrentHashMap 内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的HashTable,它们都有自己的锁,只要多个修改操作发生在不同的段上,它们就可以并发执行,把一个整体分成16个段,也就是最高支持16个进程的并发修改操作。这就是多线程中减少锁的粒度从而降低锁竞争的一种解决方案,并且代码中大多共享变量使用volatile关键字声明,目的是第一时间获取修改的内容,性能非常好。
ConcurrentSkipListMap支持并发排序功能,功能类似于TreeMap
模拟 LinkedBlockingQueue
public class MyQueue {
private LinkedList<Object> list= new LinkedList<Object>();
private AtomicInteger count = new AtomicInteger(0);
private final int min = 0;
private final int max;
public MyQueue(int size) {
this.max = size;
}
private final Object lock = new Object();
public void put(Object obj) {
synchronized (lock) {
while(count.get() == this.max) {
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
list.add(obj);
count.incrementAndGet();
lock.notify();
System.out.println("新加入的元素为:"+obj);
}
}
public Object take() {
Object obj = null;
synchronized (lock) {
while(count.get() == min) {
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
obj = list.removeFirst();
count.decrementAndGet();
lock.notify();
System.out.println("取出的元素为:"+obj);
return obj;
}
}
}
Copy-On-Write容器
简称COW,是一种程序设计中的优化策略。分为CopyOnWriteArrayList和CopyOnWriteArraySet。
CopyOnWrite容器就是写时复制的容器,通俗的理解就是我们往一个容器里面添加元素的时候,不直接往当前容器添加,添加完之后再将原元素的引用指向新的容器,这样的好处就是我们可以对CopyOnWrite容器并发的读而不用加锁,因为当前容器不会添加任何元素,所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。推荐在读多写少的场景下使用。并发写的时候也是使用加锁机制的。
并发Queue
一种以ConcurrentLinkedQueue为代表的高性能队列,一种以BlockingQueue接口为代表的阻塞队列。都继承自Queue。
ConcurrentLinkedQueue,适用于高并发场景下的队列,通过无锁的方式,实现了高并发下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueue。它是基于链接节点的无界线程安全队列,遵循先进先出原则,不允许插入null元素。
重要方法:
Add()和offer()都是添加元素的方法。在这里这两个方法是没有区别的,因为是非阻塞的,不会考虑大小问题和需不需要等待问题。
Poll()和peek(),都是取元素的方法,前者会删除元素,后者不会。
BlockingQueue接口
ArrayBlockingQueue: 基于数组的阻塞队列实现,在内部维护着一个定长数组,以便缓存队列中的数据对象,其内部没实现读写分离,也就意味着生产和消费不能完全并行,长度是需要定义的,可以指定先进先出或者先进后出,也叫有界队列。
LinkedBlockingQueue:基于链表的阻塞队列实现,在内部维护着一个链表数据缓冲队列,之所以可以高效的处理并发数据是因为内部采用分离锁(读写分离两个锁),实现生产者和消费者操作完全的并发执行。
SynchronousQueue:一种没有缓冲的队列,生产者生产的数据会直接被消费者获取并消费。加一个元素如果不取出来就会报Queue full。
PriorityBlockingQueue:基于优先级的阻塞队列,优先级的判断通过构造函数传入的Compator对象来决定,就是说传入Queue的对象必须实现comparable接口,在实现PriorityBlockingQueue的时候,内部控制线程同步的锁采用的是公平锁,它也是一个无界的队列。
它的排序机制不是在元素add入Queue的时候进行排序,而是在进行take操作的时候进行排序,这样可以实现高效率,不用在每次放元素的时候都进行一次排序操作,而take一次后Queue元素就进行排序,之后take就不用再排序了,除非就新增了元素。
DelayQueue:带有延迟时间的Queue,其中的元素只有当指定的时间到了,才能从队列中获取到该元素,该Queue的元素必须实现Delayed接口,DelayQueue是一个没有大小限制的队列,应用场景很多,比如对缓存超时的数据进行移除,任务超时处理,空闲连接的关闭等等。
//相互比较排序用
@Override
public int compareTo(Delayed delayed) {
WangMin w = (WangMin)delayed;
return this.getDelay(this.timeUnit) - w.getDelay(this.timeUnit) >0 ?1:0;
}
// 用来判断是否到了下机时间
@Override
public long getDelay(TimeUnit unit) {
//return unit.convert(endTime, TimeUnit.MILLISECONDS) - unit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
return endTime - System.currentTimeMillis();
}
多线程的设计模式
Future 模式
类似于当我们发送Ajax请求的时候,页面是异步的进行后台的处理,用户无须一直等待请求的结果,可以继续浏和操作其他内容。
FutureData 一个外壳 加载真正的data
public class FutureData implements Data {
private RealData realData;
private boolean isReady = false;
public synchronized void setRealData(RealData realData) {
//如果realData装载完毕,直接返回
if(isReady) {
return;
}
//如果没装载,装载真实对象
this.realData = realData;
isReady = true;
notify();
}
@Override
public synchronized String getRequest() {
//如果realData没装载好,程序一直处于堵塞状态
while(!isReady) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//装载好了就直接返回数据
return this.realData.getRequest();
}
RealData 真正的data,在后台执行,执行耗时的操作,查询真正的数据
public class RealData implements Data {
private String result;
@Override
public String getRequest() {
return result;
}
public RealData(String queryStr) {
System.out.println("根据"+queryStr+"进行查询,这是一个很耗时的操作...");
try {
//模拟操作时长
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("操作完毕,获取结果");
result = "查询结果";
}
}
Master-Worker模式
常用的并行计算模式。它的核心思想是系统由两类进程协作工作:Master进程和Worker进程。Master进程负责接收和分配任务,Worker负责处理子任务。当各个Worker进程处理完成后,将结果返回给Master,由Master作归纳和总结。其好处就是将一个大任务分解成若干个小任务,并行执行,从而提高系统的吞吐量。
新建一个Master
public class Master {
//1.一个盛放任务的容器
private ConcurrentLinkedQueue<Task> workQueue = new ConcurrentLinkedQueue<Task>();
//2.一个盛放Worker的集合
private HashMap<String, Thread> workers = new HashMap<String, Thread>();
//3.一个存放每一个Worker执行结果的集合
private ConcurrentHashMap<String, Object> resultMap = new ConcurrentHashMap<String, Object>();
//4.构造函数
public Master(Worker worker, int workercount) {
worker.setWorkQueue(this.workQueue);
worker.setResultMap(this.resultMap);
for(int i =0;i<workercount;i++) {
this.workers.put(String.valueOf(i), new Thread(worker));
}
}
//5.一个提交任务的方法
public void submit(Task task) {
this.workQueue.add(task);
}
//6.一个执行的方法
public void execute() {
for(Map.Entry<String, Thread> me :workers.entrySet()) {
me.getValue().start();
}
}
//7.判断是否结束
public boolean isComplete() {
for(Map.Entry<String, Thread> me :workers.entrySet()) {
if(me.getValue().getState() != Thread.State.TERMINATED) {
return false;
}
}
return true;
}
//8.计算结果
public int getResult() {
int priceResult = 0;
for(Map.Entry<String, Object> me : resultMap.entrySet()) {
priceResult += (Integer)me.getValue();
}
return priceResult;
}
}
新建一个Worker
public class Worker implements Runnable {
private ConcurrentLinkedQueue<Task> workQueue;
private ConcurrentHashMap<String, Object> resultMap;
public void setWorkQueue(ConcurrentLinkedQueue<Task> workQueue) {
this.workQueue = workQueue;
}
public void setResultMap(ConcurrentHashMap<String, Object> resultMap) {
this.resultMap = resultMap;
}
@Override
public void run() {
while(true) {
Task input = this.workQueue.poll();
if(input == null) break;
Object object = handle(input);
this.resultMap.put(String.valueOf(input.getId()), object);
}
}
public Object handle(Task input) {
Object output = null;
try {
Thread.sleep(500);
output = input.getPrice();
} catch (InterruptedException e) {
e.printStackTrace();
}
return output;
}
}
生产者消费者 模式
生产者消费者是一个非常经典的多线程模式,在这个模式中,通常由两类进程组成,即若干个生产者的线程和若干个消费者的线程。生产者线程负责提交用户请求,消费者线程负责处理生产者提交的任务,在生产者和消费者之间通过共享内存缓存区进行通信。这个共享内存缓冲区可以是消息中间件。
起生产者线程
public class Provider implements Runnable {
//共享缓存区
private BlockingQueue<Data> queue;
//多线程是否启动变量,有强制性从内存中刷新的功能,即时返回线程的状态
private volatile boolean isRunning = true;
//id生成器
private static AtomicInteger count = new AtomicInteger();
//随机对象
private static Random r = new Random();
public Provider(BlockingQueue<Data> queue) {
this.queue = queue;
}
@Override
public void run() {
while(isRunning) {
try {
//随机休眠0到1秒,表示获取数据的时间
Thread.sleep(r.nextInt(1000));
//获取的数据进行累积
int id = count.incrementAndGet();
//比如通过getData获取数据
Data data = new Data(String.valueOf(id),"数据"+id);
System.out.println("当前线程"+Thread.currentThread().getName()+",加载到公共缓冲区中,id为" + data.getId()+ ",数据为"+data.getName());
if(!this.queue.offer(data, 2, TimeUnit.SECONDS)) {
System.out.println("提交缓冲区失败!");
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void stop() {
this.isRunning = false;
}
}
起消费者线程
public class Consumer implements Runnable {
private BlockingQueue<Data> queue;
public Consumer(BlockingQueue<Data> queue) {
this.queue = queue;
}
private Random r = new Random();
@Override
public void run() {
while(true) {
try {
Data data = this.queue.take();
Thread.sleep(r.nextInt(1000));
System.out.println("当前消费线程:"+Thread.currentThread().getName() + ",消费成功,消费线程id为"+data.getId()+",消费线程数据为"+data.getName());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
运行测试
BlockingQueue<Data> queue = new LinkedBlockingQueue<Data>(10);
//生产者
Provider p1 = new Provider(queue);
Provider p2= new Provider(queue);
Provider p3 = new Provider(queue);
//消费者
Consumer c1 = new Consumer(queue);
Consumer c2 = new Consumer(queue);
Consumer c3 = new Consumer(queue);
//创建线程池运行,这是一个缓存的线程池,可以创建无穷大的线程,没有任务的时候不创建线程。空闲线程的存活时间为60s(default)
ExecutorService cachePool = Executors.newCachedThreadPool();
cachePool.execute(p1);
cachePool.execute(p2);
cachePool.execute(p3);
cachePool.execute(c1);
cachePool.execute(c2);
cachePool.execute(c3);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
p1.stop();
p2.stop();
p3.stop();
有种模拟消息中间件的赶脚