并发编程中级篇

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();

有种模拟消息中间件的赶脚

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值