高并发之同步容器

一、采用传统的方法 

/**
 * 经典面试题:写一个固定容量的容器,拥有put和get方法,以及getCount方法
 * 能够支持2个生产者线程以及10个消费者线程的阻塞调用
 * 
 * 点:生产者消费者模式
 * 
 * 如果调用 get方法时,容器为空,get方法就需要阻塞等待
 * 如果调用 put方法时,容器满了,put方法就需要阻塞等待
 * 
 * 实现方式:
 * 1. wait/notify
 * 2. Condition
 */
public class MyContainer1<T> {
    
    private final LinkedList<T> list = new LinkedList<>();
    private final int MAX = 10;
    private int count = 0;

    public synchronized void put(T t) {
        while (MAX == count) { // 如果容量最大,释放锁等待    ///【这里为什么使用while,而不是使用if???】
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 否则 put 
        list.add(t);
        ++count;
        this.notifyAll(); // 通知消费者线程,可以消费了
        // 【这里为什么调用 notifyAll 而不是 notify ?】
    }

    public synchronized T get() {
        while (list.size() == 0) { // 如果容量为空,释放锁等待  
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 否则获取
        T t = list.removeFirst();
        count--;
        this.notifyAll(); // 通知生产者线程生产
        return t;
    }
}

/*
为什么使用while 而不是使用 if ???
在与wait()的配合中,百分之99的程序都是与while而不是if结合使用。
上述代码中,在容器已满的情况下,put方法会wait等待,当容器中的元素被消费者消费了一部分,就会唤醒所有put方法,
put方法会继续向下执行,直接执行list.add(t),那么多个生产者线程执行list.add() 就有可能出现数据一致性的问题。
如果使用while则会循环判断,就避免了这些问题。
不是有锁吗?为什么会需要循环判断?
wait之后,锁就会失去,再次被唤醒时,并且得到锁之后,**是从list.add()开始执行的**,会无判断直接加入到容器中。
为什么调用 notifyAll 而不是 notify ?
因为notify有可能再次叫醒一个生产者线程
 */

 

二、用reentranLock

/**
 * 经典面试题:写一个固定容量的容器,拥有put和get方法,以及getCount方法
 * 能够支持2个生产者线程以及10个消费者线程的阻塞调用
 * <p>
 * 点:生产者消费者模式
 * <p>
 * 如果调用 get方法时,容器为空,get方法就需要阻塞等待
 * 如果调用 put方法时,容器满了,put方法就需要阻塞等待
 * <p>
 * 实现方式:
 * 1. wait/notify
 * 2. Condition
 * <p>
 * <p>
 * 使用Lock和Condition实现,可以精确唤醒某些线程
 */
public class MyContainer2<T> {

    private final LinkedList<T> list = new LinkedList<>();
    private final int MAX = 10;
    private int count = 0;

    private Lock lock = new ReentrantLock();
    private Condition producer = lock.newCondition();
    private Condition consumer = lock.newCondition();


    public synchronized void put(T t) {
        lock.lock();
        try {
            while (MAX == count) {
                producer.await();
            }
            list.add(t);
            ++count;
            consumer.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public synchronized T get() {
        lock.lock();
        try {
            while (list.size() == 0) {
                consumer.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        T t = list.removeFirst();
        count--;
        producer.signalAll();
        return t;
    }

    public static void main(String[] args) {
        MyContainer2<String> c = new MyContainer2<>();
        // 启动消费者线程
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 5; j++) {
                    System.out.println(c.get());
                }
            }, "c_" + i ).start();
        }

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (int i = 0; i < 2; i++) {
            new Thread(()->{
                for (int j = 0; j < 2; j++) {
                    c.put(Thread.currentThread().getName() + " " + j);
                }
            }, "p_" + i).start();
        }
    }
}

 

上传项目不支持Firefox,提示代码附件太大(1.4M),我写了30多分钟的描述全没了,太坑爹了。 10分有点贵,绝对原创,共2个代码文件300多行,下载请谨慎。你下载了,若绝对不爽在评论中说出来,不要让其他同学上当,如果觉得还可以也请留言。 代码采用多工作者多线程执行任务。通过暴露的方法往工作者传递消息,然后采用事件回调返回处理结果,实现的事件有OnThreadComplete,OnAddedTask,OnStart,OnSuccess,OnFailure,OnTimeout。 事件回调支持同步或异步,每工作者可以指定执行超时时间,避免线程阻塞死掉。队列采用线程安全的BlockingCollection,每组工作者用一个队列。委托采用Func来定义的,没有采用传统且不太好理解的Delegate。这让代码减少很多,也更容易理解。多线程应该采用消息中心来交换数据,这样就规避了线程同步交互,等待,阻塞等等,全部是异步调用,全部是接收消息工作,然后产生消息,线程间没有耦合,消息中心有很多成熟的方案如RabbitMQ, Redis(里面有简单的消息交换),微软有消息云服务等。如果应用不复杂,可以采用DB做个简单的消息中心,建议采用HTTP接口来获取与写入消息,方便将来升级重构消息中心。 开发环境VS2012,Framework4.0,代码注释量很大,如果你高兴这代码你可以随意蹂躏,如果你有建设性意见请告诉我。 下面是部分测试代码: //发送消息方法容器 var msgContainer = new Hashtable(); //创建并启动工作者 foreach (var key in workers.Keys) { //创建工作者 //启动5个线程,异步事件回调,方法执行20秒超时,程序跑起来有100个线程,由于引入超时控制,实际线程将达100+50 //下面的20个工作组,有5个是超时的,主要测试OnTimeout事件,你可以设置seleep的时间来控制 //我把sleep的时间设置的有点长,方便你测试 //测试的时候你会看见有异常,那是应为Timeout我采用的是Thread.Abort方法,这样才出发了ontimeout事件 var worker = new Sehui.Worker(5, key.ToString(), (Func)workers[key], false, new TimeSpan(0, 0, 20)); worker.OnStart += worker_OnEvent; worker.OnSuccess += worker_OnEvent; worker.OnFailure += worker_OnEvent; worker.OnTimeout += worker_OnEvent; //启动工作者 worker.Start(); //将增加消息方法放到Hashtable中 //这里我是偷懒,下面可以用循环的方式往线程中add message msgContainer.Add(key.ToString(), new Func(worker.AddTask)); } //向20个工作者发送消息,每个工作者发送20条消息 for (var i = 0; i < 20; i++) { for (var k = 0; k < 20; k++) { ((Func)msgContainer["SyncDb" + k])("[Work " + k + "] Message " + i); Console.WriteLine("send msg to worker{0},msgid:{1}", k, i); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值