zoukankan      html  css  js  c++  java
  • 多线程初步小结

    0.为什么需要多线程

    cpu太快,其他硬件太慢,如网络,硬盘等。所以开多个线程,进程,让cpu在等待网络的时候也可以做其他线程。

    这样就会出现多线程访问同一数据的竞争问题,所以需要把访问共享数据的代码块做成线程安全的。

    注意访问共享数据需要锁住,而访问耗时的网络等必须在锁之外。否则变成了类似单线程,没有意义。

    1.多线程存在什么问题

    数据竞争:如  i++ ,链表的删除和插入多线程同时进行。
    竞态条件:如  if(i==0){xxx这里i可能已经变化了。还在执行某些操作xx}

    2.问题归纳一下,为什么会有这些问题

    非原子操作和指令重排序,常出现于先检查后执行的情况。

    3.如何解决

    加锁,让一段逻辑成为一个原子操作。常见的是把 共享数据全部放入一个类中来管理。

    3.1所以我们需要有个机制来让线程阻塞和激活。这个就是lock和unlock. 

    lock 必须要硬件支持,因为 检测是否有线程进入,有如何,没如何,这个逻辑必须是原子的。不然自己都不是原子,如何去锁定一段代码为原子操作。

    lock让线程挂起。unlock让阻塞线程都去抢锁。只要保证锁定的地方是必须锁定的,也就是指只锁定读写共享数据的部分代码。那么就已经就性能极限了。

    等等,因为我们是解决数据共享问题。必然有插入和读取,我们就是保护读写之间的安全性。

    那么关于多线程,  还发现一种特殊情况。比如共享数据空了, 那么读线程还是会去获得锁,不过逻辑上我们应该会让他立即放锁,不作任何处理。但是这个不是最优的处理。应该让他没有数据之前别再去请求了。

    3.2所以我们condition。让满足某个条件的线程不再放入到阻塞队伍中。而是放到等待队伍中。当然我们也必须要对应的线程来唤醒。nitify 或者 singal .

    注意unlock,只让lock的线程去抢cpu。而notify 是让等待的队列去抢cpu。lock和wait分别形成了2个不同的队列。一般是要先nofity再 unlock.

    还有一点需要注意 wait之后会放锁,自己进入等待,有信号后,必须先获得锁。所以隐含语意就是 xx.await  等价于 xx.unlock.  xx.等待唤醒信号  xx.lock .

    而一般获得锁需要再次判断条件,因为notify不能保证之前条件还是成立,而且获得锁之后是继续执行wait后的语句。 所以一般是while(xxx){xxx.await}  ,用一个循环,让它唤醒后再次检测,算是固定写法。

    理论上至此好像没有任何问题了。

    但是需求是多样的。比如读写问题,需要读可以多线程访问。

    所以我们要把把lock的作用扩大化。就是lock本来是判断是否有线程进入,我们用lock作为逻辑太小用了。

    我们应该用lock锁住自定义数据,让这些自定义数据来组成我们的逻辑。

    如一下逻辑,但是系统已经提供了读写锁。而且实现原理应该不是一样。所以没有必要自己写。

    3.3如果确实需要更复杂的逻辑,如读锁,那么我们可以让lock。锁住2个变量,分为为读的线程个数,和写的线程个数,处理后,立即放锁。 也就是把实际读数据的部分不上锁,而锁住一些自定义数据来实现逻辑。

    那么理论上好像可以满足任何需求了

    3.4通用的做法。就是把多线程下,会共享的数据放入到一个类中。

    再类中,写线程安全的方法,提供给外部使用。

    要注意的一点就是,竞态条件。所以这个必须人工去检测,所有涉及到共享变量的地方,看看是否存在 先判断,后处理的情况。

    如果有,必须把整段逻辑放入某个方法中,提供给外部使用。

    public synchronized void lockRead() throws InterruptedException {  
            while (writers > 0 || writeRequests > 0) {                     
                wait();                                                    
            }                                                              
    
            readers++;                                                     
        }    

    4.解决示例

    java,c#,c++ 的消费者和生产者模式。

    5.解决方案是否有其他问题。

    死锁等问题。一般是需要资源的全部获取。所以对资源的获取进行固定排序是一个方式。

    记住sleep, yield。不会放锁。所以处理满数据的时候,让写线程sleep ,是不会让读线程获得cpu的。必须wait.放锁,并进入等待队列。或者直接跳过代码进入unlock.

    6.小结。

    之前写的互斥量的理解https://www.cnblogs.com/lsfv/p/6284735.html 

    7. lock.unlock.   await. signle.

     lock.unlock为什么是一等公民?因为lock.unlock 是由系统控制的,只要有cpu空,就会随时排到你。起码他可以排队。别人unlok后。他就可以参与lock.

    await. signle.为什么是二等公民,因为await. signle是由对象控制的。首先要一个对象获得锁才能唤醒你们。操作系统无法唤醒他们。  已经丧失了排队的资格,unlock了。只能由一等公民去唤醒他。

    当一个线程尝试着lock一个同步对象的时候,该线程就在就绪队列中排队。

    一旦unlock没人拥有该同步对象,就绪队列中的线程就可以占有该同步对象。这也是我们平时最经常用的lock方法。
    为了其他的同步目的,占有同步对象的线程也可以暂时放弃同步对象,并把自己流放到等待队列中去。这就是Monitor.Wait。由于该线程放弃了同步对象,其他在就绪队列的排队者就可以进而拥有同步对象。
    比起就绪队列来说,在等待队列中排队的线程更像是二等公民:他们不能自动得到同步对象,甚至不能自动升舱到就绪队列。而Monitor.Pulse的作用就是开一次门,使得一个正在等待队列中的线程升舱到就绪队列;

    相应的Monitor.PulseAll则打开门放所有等待队列中的线程到就绪队列。

  • 相关阅读:
    Servlet学习总结
    Tomcat学习总结1
    第44周星期日反思
    第44周星期一Tomcat学习2
    第44周星期五忙碌文档的一天
    第44周星期六好文章摘录
    laravel 5.6接入微信第三方授权登陆的主要步骤
    laravel多表登录出现路由调用错误
    cURL error 60: SSL certificate problem...
    传说中Python最难理解的点|看这完篇就够了(装饰器)
  • 原文地址:https://www.cnblogs.com/lsfv/p/11013900.html
Copyright © 2011-2022 走看看