• 多线程 消费和生产模式


    一。自行编写线程安全的List来作为作为消费者和生产者的数据类型。

    从1.1和1.2可以看出明显差距.

    首先object的锁把互斥量放在object上。而lock类的单独出现一个lock的变量,更灵活。

    更大的区别是object 锁的wait,notifiyall是对象级别。而lock的 await,single 是条件等待。同一把锁可以分情况唤醒不同的线程,readwait.nofityall(),唤醒卡在读上的线程。     而object.nofityall,把这个锁上的全部线程都唤醒了。

    1.1object 锁

    public static class MySafeList
        {
            private List<Integer> mData=new ArrayList<>();
            private int num=0;
            public Integer insertID()
            {
                try
                {
                    Thread.sleep(1000);
                }
                catch (Exception e)
                {
                    LSLog.Log_Exception(e);
                }
    
                synchronized (this)
                {
                    try
                    {
                        while (mData.size() > 10)
                        {
                            this.wait();
                        }
                        mData.add(num);
                        num++;
                        this.notifyAll();
                        return num-1;
                    }
                    catch (Exception e)
                    {
                        return null;
                    }
                }
            }
    
            public Integer getID()
            {
                try
                {
                    Thread.sleep(2000);
                } catch (Exception e)
                {
                    LSLog.Log_Exception(e);
                }
    
                synchronized (this)
                {
                    try
                    {
                        while (mData.size()==0)
                        {
                            this.wait();
                        }
    
                        int res=mData.get(0);
                        mData.remove(0);
                        this.notifyAll();
                        return res;
                    }
                    catch (Exception e)
                    {
                        return null;
                    }
                }
            }
        }

     测试方法

     private void MultipleThread()
        {
            final MySafeList mySafeList=new MySafeList();
            Thread thread_produce1=new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    while (true)
                    {
                        Integer insered= mySafeList.insertID();
                        if(insered!=null)
                        {
                            LSLog.Log_INFO("insert:" + insered);
                        }
                    }
                }
            });
            thread_produce1.start();
    
            Thread thread_produce2=new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    while (true)
                    {
                        Integer insered= mySafeList.insertID();
                        if(insered!=null)
                        {
                            LSLog.Log_INFO("insert:" + insered);
                        }
                    }
                }
            });
            thread_produce2.start();
    
            Thread thread_consume1=new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    while (true)
                    {
                        Integer id = mySafeList.getID();
                        LSLog.Log_INFO("get:" + id);
                    }
                }
            });
            thread_consume1.start();
    
            Thread thread_consume2=new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    while (true)
                    {
                        Integer id = mySafeList.getID();
                        LSLog.Log_INFO("get:" + id);
                    }
                }
            });
            thread_consume2.start();
        }

    1.2用reentantlock来锁定。用condition ,singel来处理2种极端情况。并把共享的数据放入到类ThreadsPractice2中。由类来管理。就和java自带的blockingqueue一样。自己管理线程安全。只提供对外方法 get 和 add

    而外部调用者不需要考虑任何锁。只需要开线程读和写。

    package com.linson.android.hiandroid2.JavaPractice;
    import com.linson.LSLibrary.AndroidHelper.LSComponentsHelper;
    import java.util.LinkedList;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    //线程安全的list
    public class ThreadsPractice2
    {
        private LinkedList<Integer> mBookID=new LinkedList<>();
        private ReentrantLock mReentrantLock=new ReentrantLock();
        private Condition full=mReentrantLock.newCondition();
        private Condition empty=mReentrantLock.newCondition();
    
        public Integer getBookID()
        {
            mReentrantLock.lock();
            try
            {
                while (mBookID.size()<=0)
                {
                    empty.await();
                }
                int id = mBookID.removeFirst();
                full.signalAll();
                return id;
    
            } catch (Exception e)
            {
                LSComponentsHelper.LS_Log.Log_Exception(e);
                return 0;
            }
            finally
            {
                mReentrantLock.unlock();
            }
        }
    
        public void addBookID(int id)
        {
            mReentrantLock.lock();
            try
            {
                while (mBookID.size()>=100)
                {
                    full.await();
                }
                mBookID.add(id);
                empty.signalAll();
            } catch (Exception e)
            {
                LSComponentsHelper.LS_Log.Log_Exception(e);
            }
            finally
            {
                mReentrantLock.unlock();
            }
        }
    }

    测试使用线程安全的list

    private void productConsume()
        {
            ThreadsPractice2 threadsPractice2=new ThreadsPractice2();
    
            Thread w1=new Thread(new Writer(threadsPractice2, 1, 10000));
            Thread w2=new Thread(new Writer(threadsPractice2, 10001, 20000));
            w1.start();
            w2.start();
    
    
            Thread r1=new Thread(new Reader(threadsPractice2));
            Thread r2=new Thread(new Reader(threadsPractice2));
            r1.start();
            r2.start();
        }
    
        private class Writer implements Runnable
        {
            private ThreadsPractice2 mData;
            private int mstart,mend;
            public Writer(ThreadsPractice2 threadsPractice2,int satrt,int end)
            {
                mData=threadsPractice2;
                mstart=satrt;
                mend=end;
            }
            @Override
            public void run()
            {
                long startTime= System.currentTimeMillis();
    
                for (int i=mstart;i<=mend;i++)
                {
                    mData.addBookID(i);
                }
    
                long endTime=System.currentTimeMillis();
                LSComponentsHelper.LS_Log.Log_INFO(Thread.currentThread().getName()+"use millseconds:"+(endTime-startTime)+". maxid:");
            }
        }
    
        private class Reader implements Runnable
        {
            private ThreadsPractice2 mData;
            public Reader(ThreadsPractice2 threadsPractice2)
            {
                mData=threadsPractice2;
            }
            @Override
            public void run()
            {
                while (true)
                {
                    int bookid=mData.getBookID();
                    if(bookid>0)
                    {
                        LSComponentsHelper.LS_Log.Log_INFO("bookid:"+bookid);
                    }
                }
            }
        }

    消费,生产例子2

       //region Product and consume
        private static Stack<Integer> mData=new Stack<>();
        private static Integer mNums=0;
        private static ReentrantLock mlock=new ReentrantLock();
        private static Condition mEmpty=mlock.newCondition();
        private static Condition mFull=mlock.newCondition();
    
    
        private void produceAndConsume()
        {
            Thread p1=new Thread(new Producter(),"p1");
            Thread p2=new Thread(new Producter(),"p2");
    
            Thread c1=new Thread(new Consume(),"c1");
    
            p1.start();
            p2.start();
            c1.start();
        }
    
    
        private class  Producter implements Runnable
        {
            @Override
            public void run()
            {
                while (true)
                {
                    try
                    {
                        Thread.sleep(3000);//模拟一个耗时的操作。注意,必须放到锁外部,不然没意义。所以我们才需要多线程。
                    } catch (Exception e)
                    {
                        LSComponentsHelper.LS_Log.Log_Exception(e);
                    }
                    mlock.lock();
                    try
                    {
                        while (mData.size()>=15)
                        {
                            mFull.await();
                        }
    
                        mData.push(mNums);
                        LSComponentsHelper.LS_Log.Log_INFO(Thread.currentThread().getName()+". add "+mNums.toString()+".         size:"+mData.size());
                        mNums+=1;
                        mEmpty.signal();
                    } catch (Exception e)
                    {
                        LSComponentsHelper.LS_Log.Log_Exception(e);
                    } finally
                    {
                        mlock.unlock();
                    }
                }
            }
        }
    
        private class Consume implements Runnable
        {
            @Override
            public void run()
            {
                while (true)
                {
                    try
                    {
                        Thread.sleep(6000);//模拟处理很慢,必须放入到锁外。<1.5秒。读快,集合数据处于处理完的空状态。 >1.5秒读慢,集合数据处于累加状态,最后保持满状态,处理太慢。 可以用 1000和6000来测试。
                    } catch (Exception e)
                    {
                        LSComponentsHelper.LS_Log.Log_Exception(e);
                    }
                    mlock.lock();
                    try
                    {
                        while (mData.size() <= 0)
                        {
                            mEmpty.await();
                        }
                        Integer getn=mData.pop();
                        LSComponentsHelper.LS_Log.Log_INFO(Thread.currentThread().getName()+"reader:"+getn.toString()+".size:"+mData.size());
                        mFull.signal();
    
                    } catch (Exception e)
                    {
                        LSComponentsHelper.LS_Log.Log_Exception(e);
                    } finally
                    {
                        mlock.unlock();
                    }
                }
            }
        }
        //endregion

     2.使用系统提供的blockingQueue

    queue的size是固定的,建立了后就不能修改。故意设置小一点。看了下实现是一个数组【】,所以会作为一个环形缓冲?

    为了测试queue,设计了一个小方法,2个写线程,分别写10000个数据。 3个读线程吧数据读到自己的set中。那么只要3个set的总数等于写的数量。那么就可以确定,写线程没有重复和丢失任何数据。同时也可以证明,读线程,也没有丢失和重复。

    呵呵,自带的当然不会错。结果: 7063+6633+6304=20000.应付20000个数字。基本是毫秒级。所以非常好。

    不知道c#有没有线程安全的队列,反正c++是不自带。有blockingQueue和readWriterLock. 那基本上java碰到多线程,可以用这2个方案,可以解决绝大部分问题啊。

    package com.linson.android.hiandroid2.JavaPractice;
    
    import com.linson.LSLibrary.AndroidHelper.LSComponentsHelper;
    
    import java.util.HashSet;
    import java.util.Set;
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    
    public class BlockQueueTest
    {
        private BlockingQueue<Integer> mBlockingQueue=new ArrayBlockingQueue<>(100);
        private Set<Integer> thread1Set=new HashSet<>();
        private Set<Integer> thread2Set=new HashSet<>();
        private Set<Integer> thread3Set=new HashSet<>();
        public void Start()
        {
            Thread w1=new Thread(new Writer(mBlockingQueue,1,10000));
            Thread w2=new Thread(new Writer(mBlockingQueue,10001,20000));
    
            Thread r1=new Thread(new Reader(mBlockingQueue),"thread1");
            Thread r2=new Thread(new Reader(mBlockingQueue),"thread2");
            Thread r3=new Thread(new Reader(mBlockingQueue),"thread3");
    
            w1.start();
            w2.start();
            r1.start();
            r2.start();
            r3.start();
    
            try
            {
                w1.join();
                w2.join();
                LSComponentsHelper.LS_Log.Log_INFO(thread1Set.size()+"."+thread2Set.size()+"."+thread3Set.size()+".");
                Thread.sleep(5000);
            } catch (Exception e)
            {
                LSComponentsHelper.LS_Log.Log_Exception(e);
            }
    
            LSComponentsHelper.LS_Log.Log_INFO(thread1Set.size()+"."+thread2Set.size()+"."+thread3Set.size()+".");
        }
    
        private class Writer implements Runnable
        {
            private BlockingQueue<Integer> mdata;
            private int mStart=0;
            private int mEnd=0;
            public Writer(BlockingQueue<Integer> blockingQueue,int start,int end)
            {
                mdata = blockingQueue;
                mStart=start;
                mEnd=end;
            }
            @Override
            public void run()
            {
                for(int i=mStart;i<=mEnd;i++)
                {
                    try
                    {
                        mdata.put(i);
                    } catch (Exception e)
                    {
                        LSComponentsHelper.LS_Log.Log_Exception(e);
                    }
                }
            }
        }
    
        private class Reader implements Runnable
        {
            private BlockingQueue<Integer> mdata;
            public Reader(BlockingQueue<Integer> blockingQueue)
            {
                mdata = blockingQueue;
            }
            @Override
            public void run()
            {
                while (true)
                {
                    try
                    {
                        int res = mdata.take();
                        if(Thread.currentThread().getName()=="thread1")
                        {
                            thread1Set.add(res);
                        }
                        if(Thread.currentThread().getName()=="thread2")
                        {
                            thread2Set.add(res);
                        }
                        if(Thread.currentThread().getName()=="thread3")
                        {
                            thread3Set.add(res);
                        }
                    } catch (Exception e)
                    {
                        LSComponentsHelper.LS_Log.Log_Exception(e);
                    }
                }
            }
        }
    }

    3.利用系统自带读写锁。来做一个线程安全的读写缓冲区。

    所以可以看到。我们自己做的也是做出一个线程安全的list。系统自带的blockqueue,也是一个线程安全的队列。

    而读写锁。我们一般也会根据具体情况。先做一个自己的线程安全的缓冲区。

    所以多线程的最佳实践就是用类来 处理共享数据。保证线程安全。

    package com.linson.android.hiandroid2.JavaPractice;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class SafeReadWriterCache<key,value>
    {
        private Map<key,value> mMap=new HashMap<>();
        private ReentrantReadWriteLock mReentrantReadWriteLock=new ReentrantReadWriteLock();
    
        public value get(key k)
        {
            value res=null;
            mReentrantReadWriteLock.readLock().lock();
            try
            {
                res=mMap.get(k);
            }
            finally
            {
                mReentrantReadWriteLock.readLock().unlock();
            }
            return res;
        }
    
        public void set(key kk,value vv)
        {
            mReentrantReadWriteLock.writeLock().lock();
            try
            {
                mMap.put(kk, vv);
            }
            finally
            {
                mReentrantReadWriteLock.writeLock().unlock();
            }
        }
    
        public void clear()
        {
            mReentrantReadWriteLock.writeLock().lock();
            try
            {
                mMap.clear();
            }
            finally
            {
                mReentrantReadWriteLock.writeLock().unlock();
            }
        }
    }

    以下例子错误,有空再修正吧。先放到这里。

    1.非常简单的,一线程写,一线程读。 读线程阻塞,直到写线程通知读线程。简单,重要是基本不会用错。

    #include <stdio.h>
    #include <string>
    #include <iostream>
    #include <memory>
    #include <mutex>
    #include <condition_variable>
    #include <vector>
    #include <thread>
    #include <unistd.h>
    
    using namespace std;
    
    class book
    {
    public:
        book():canread(false){}
        
        void Write(const vector<string>& _v)
        {
            unique_lock<mutex> w_mtx(book_mtx,defer_lock);//控制代码1
            w_mtx.lock();//控制代码2
            
            
            
            cout<<this_thread::get_id()<<"  :writer get lock, and start write"<<endl;
            
            cout<<"before write.vector size:"<<books.size()<<endl;
            
            
            for(size_t i=0;i<_v.size();i++)
            {
                books.push_back(_v[i]);
            }
            
            cout<<"after write.vector size:"<<books.size()<<endl;
        
            cout<<this_thread::get_id()<<"  :write end and ready set canread=true, and notify all"<<endl;
            
            canread=true;////控制代码3
            w_mtx.unlock();//控制代码4
            book_cv.notify_all();//控制代码5
        }
        
        void Read()
        {
            unique_lock<mutex> r_mtx(book_mtx,defer_lock);//控制代码1
            r_mtx.lock();//控制代码2
            while(!canread)//控制代码3
            {
                book_cv.wait(r_mtx);//控制代码4
            }
            cout<<this_thread::get_id()<<"  :reader get lock.and set value='' , canread=false "<<endl;
            
            cout<<"before read.vector size:"<<books.size()<<endl;
            vector<string> empty;
            empty.swap(books);
            cout<<"after read.vector size:"<<books.size()<<endl;
        
            
            canread=false;//控制代码5
            r_mtx.unlock();//控制代码6
        }
        
        
    private:
        vector<string> books;
        mutex book_mtx;
        condition_variable book_cv;
        bool canread;
    };
    
    
    //写,设置为15秒一次.
    void wrtie2book(book& _v)
    {
        for(;;)
        {
            vector<string> newbooks;
            newbooks.push_back("aaa");
            newbooks.push_back("bbb");
            newbooks.push_back("ccc");
            _v.Write(newbooks);
            sleep(15);
        }
    }
    
    //读是只要可以读就一直读.不能读就阻塞在县城.等待生产者放入数据.
    void read2book(book& _v)
    {
        for(;;)
        {
            _v.Read();
        }
    }
    
    
    int main()
    {
        book mybooks;
        
    
        thread read1(read2book,std::ref(mybooks));
        read1.detach();
    
        thread wrtie2(wrtie2book,std::ref(mybooks));
        wrtie2.detach();
    
        string cmd;
        cin>>cmd;
        return 0;
    }

    2)如果有多线程读,很多场合下是,每个线程处理一条。

    代码也是基本一魔一样。只要读线程也设置一下canread 。同样简单,不出错。

    #include <stdio.h>
    #include <string>
    #include <iostream>
    #include <memory>
    #include <mutex>
    #include <condition_variable>
    #include <vector>
    #include <thread>
    #include <unistd.h>
    
    using namespace std;
    
    
    class book
    {
    public:
        book():canread(false){}
        
        void Write(const vector<string>& _v)
        {
            unique_lock<mutex> w_mtx(book_mtx,defer_lock);//控制代码1
            w_mtx.lock();//控制代码2
            
            
            
            cout<<this_thread::get_id()<<"  :writer get lock, and start write"<<endl;
            
            cout<<"before write.vector size:"<<books.size()<<endl;
            
            
            for(size_t i=0;i<_v.size();i++)
            {
                books.push_back(_v[i]);
            }
            
            cout<<"after write.vector size:"<<books.size()<<endl;
        
            cout<<this_thread::get_id()<<"  :write end and ready set canread=true, and notify all"<<endl;
            
            
            if(books.size()>0)//控制代码5
            {
                canread=true;//控制代码6
            }
            else
            {
                canread=false;//控制代码7
            }
            w_mtx.unlock();//控制代码4
            book_cv.notify_all();//控制代码5
        }
        
        void Read()
        {
            unique_lock<mutex> r_mtx(book_mtx,defer_lock);//控制代码1
            r_mtx.lock();//控制代码2
            while(!canread)//控制代码3
            {
                book_cv.wait(r_mtx);//控制代码4
            }
            
            
            cout<<this_thread::get_id()<<"  :reader get lock.and get one item  "<<endl;
            
            cout<<"before read.vector size:"<<books.size()<<endl;
            if(books.size()>0)
            {
                books.erase(books.begin());
            }
            
            
            cout<<"after read.vector size:"<<books.size()<<endl;
            cout<<this_thread::get_id()<<"  :reader check size ,and set canread true or false"<<endl;
            
            
            if(books.size()>0)//控制代码5
            {
                canread=true;//控制代码6
            }
            else
            {
                canread=false;//控制代码7
            }
            r_mtx.unlock();//控制代码8
        }
        
        
    private:
        vector<string> books;
        mutex book_mtx;
        condition_variable book_cv;
        bool canread;
    };
    
    
    //写,设置为15秒一次.
    void wrtie2book(book& _v)
    {
        for(;;)
        {
            vector<string> newbooks;
            newbooks.push_back("aaa");
            newbooks.push_back("bbb");
            newbooks.push_back("ccc");
            _v.Write(newbooks);
            sleep(15);
        }
    }
    
    //读是只要可以读就一直读.不能读就阻塞在县城.等待生产者放入数据.
    void read2book(book& _v)
    {
        for(;;)
        {
            _v.Read();
        }
    }
    
    
    int main()
    {
        book mybooks;
        
    
        thread read1(read2book,std::ref(mybooks));
        read1.detach();
        
        thread read2(read2book,std::ref(mybooks));
        read2.detach();
        
        thread read3(read2book,std::ref(mybooks));
        read3.detach();
    
    
        thread wrtie2(wrtie2book,std::ref(mybooks));
        wrtie2.detach();
    
        string cmd;
        cin>>cmd;
        return 0;
    }

     

     

    3) 读线程如果不可读会一直阻塞在锁上。

    有时候需要读线程过段时间,跳出去,执行读数据函数外面的代码。

    比如 执行关闭服务器方法。读线程就没办法关闭了。可以设置一个超时,让读线程超时后,就执行下。当然必须判断是超时引发的,还是有可读数据。

     

    unique_lock<mutex> reclck(recmtx, defer_lock);
      reclck.lock();
      while(recvBuffEnable==false)
      {
          recv_cv.wait_for(reclck,std::chrono::seconds(6));
          if(recvBuffEnable==false)
          {
            return std::vector<FDMsg>();
          }
      }

    这样6秒钟。醒一次。如果是没有数据可读。那么返回去,检查外部的关闭服务变量是否变化。

     

     

     

    c# 的话,有太多的类和语法糖。

    所以还是使用最基本的monitor和mutex 来实现锁定和同步。

    掌握基本的关键字的用法,可以组合成很多模式,避免学习各种语法糖,分散精力和浪费时间。

    需要注意的就是c#和c++的不同。 monitor.wait ,必须在 enter 和exit之间。这个和c++是不同的。开始不小心,按照C++的方式写,弹出了异常。

     

    如:c++ ,是先释放锁,后通知wait的线程。

    w_mtx.unlock();//控制代码4
    book_cv.notify_all();//控制代码5

    而c#,是先通知wait 的线程。后释放锁,
    如:
    Monitor.Pulse(mtx);
    Monitor.Exit(mtx);
     

     

    例如一个生产和消费者模式的例子。

    class Program
        {
            private static Queue<int> data_int = new Queue<int>();
    
            private static Mutex mtx = new Mutex();
    
            private static int i = 0;
    
            private static void write()
            {
                while (true)
                {
                    try
                    {
                        Monitor.Enter(mtx);
                        data_int.Enqueue(i);
                        Console.WriteLine("write:" + i);
                        i++;
                    }
                    finally
                    {
                        Monitor.Pulse(mtx);
                        Monitor.Exit(mtx);
                    }
                    Thread.Sleep(3000);//模拟外部数据,1秒获得一次
                }
            }
    
            private static void read()
            {
                while (true)
                {
                    string thread_name = Thread.CurrentThread.Name;
                    try
                    {
                        Monitor.Enter(mtx);
                        bool hasData = data_int.Count > 0;
                        if (!hasData)
                        {
                            Monitor.Wait(mtx, -1, false);
                        }//wait之后.并未返回,而是继续当前下面语句
                        if (data_int.Count > 0)
                        {
                            int i = data_int.Dequeue();
                            Console.WriteLine("read" + i);
                        }
                    }
                    finally
                    {
                        Monitor.Exit(mtx);
                    }
                }
            }
    
            static void Main(string[] args)
            {
                Thread read1 = new Thread(read);
                read1.Name = "read1";
                read1.Start();
    
                Thread read2 = new Thread(read);
                read2.Name = "read2";
                read2.Start();
    
                Thread write1 = new Thread(write);
                write1.Name = "write1";
                write1.Start();
    
                //Thread write2 = new Thread(write);
                //write2.Start();
    
            }
        }

     

    这个例子中,monitor和mutex 就类似于c++的  unique_lock 和mutex.

    Assembly code
    1
    2
    3
             |- 拥有锁的线程
    lockObj->      |- 就绪队列(ready queue)
             |- 等待队列(wait queue)
  • 相关阅读:
    除下草并推荐PhantomJS
    GTAC 2013
    Benchmark感受
    ChinaTest第二天
    "西厂"、"东厂"照片
    谈面试上
    mysql基本常用命令(转)
    java学习笔记数据类型、运算符和控制语句
    java学习笔记第一个applet程序以及一个小问题的解决
    网上阅卷系统自动识别功能代码
  • 原文地址:https://www.cnblogs.com/lsfv/p/6658672.html
Copyright © 2020-2023  润新知