zoukankan      html  css  js  c++  java
  • 多线程02_线程同步

    线程同步概念

    同步是用来解决多线程的安全问题的,在多线程中,同步能控制对共享数据的访问。如果没有同步,当一个线程在修改一个共享数据时,而另外一个线程正在使用或者更新同一个共享数据,这样容易导致程序出现错误的结果。

    线程同步方式

    同步的三种表现形式:

    • 同步代码块
      可以指定需要获取哪个对象的同步锁,使用synchronized的代码块同样需要锁,但他的锁可以是任意对象。考虑到安全问题,一般还是使用同一个对象,相对来说效率较高。
      注意:虽然同步代码快的锁可以使任何对象,但是在进行多线程通信使用同步代码快时,必须保证同步代码快的锁的对象和,否则会报错。同步函数的锁是this,也要保证同步函数的锁的对象和调用wait、notify和notifyAll的对象是同一个对象,也就是都是this锁代表的对象。
    • 同步函数
      同步方法是指进入该方法时需要获取this对象的同步锁,在方法上使用synchronized关键字,使用this对象作为锁,也就是使用了当前对象,因为锁住了方法,所以相对于代码块来说效率相对较低。
      注:意静态同步函数的锁是该方法所在的类的字节码文件对象,即类名.class文件。

    • 在jdk1.5后,用lock锁取代了synchronized,个人理解也就是对同步代码块做了修改,并没有提供对同步方法的修改,主要还是效率问题吧。
    • 使用concurrent包中的原子类来保证线程安全
      JDK提供的底层使用轻量级锁实现(CAS操作,使用unsafe类型的原子性方法实现线程安全)的原子类,例如AtomicInteger。

    各种锁的转换

    • 概念
      • 偏向锁:只有Thread#1会进入临界区
      • 轻量级锁:Thread#1和Thread#2交替进入临界区
      • 重量级锁:Thread#1和Thread#2同时进入临界区
      • 悲观锁:就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁,都是在做操作之前先上锁。比如synchronized关键字
      • 乐观锁:就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。比如volatile关键字和CAS(compare and swap 原 子操作:CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A 和内存值V相同时,将内存值V修改为B,否则什么都不做。它是非阻塞的)
      • 重入锁:锁是可以重入的。
    • 锁的转换
      • 偏向锁,轻量级锁都是乐观锁,重量级锁是悲观锁。
        一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可 偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第 一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对 比ID,不需要再使用CAS在进行操作。
        一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象 变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。
        轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。
    • 锁的升级图示

    几个方法对比

    • sleep()和wait()的区别
      • 这两个方法来自不同的类,sleep()来自Thread类,而wait()来自Object类。
      • sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。而wait()是Object类的非静态方法。
      • sleep()释放资源不释放锁,而wait()释放资源释放锁。
      • 使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。
    • wait()、sleep()、notify()、notifyAll()
      • wait()使一个线程处于等待状态,并且释放所持有的对象的lock。
      • sleep()使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
      • notify()唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程(一般是最先开始等待的线程),而且不是按优先级。
      • Allnotity()唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
    • 为什么wait()、notify()、notifyAll()这些用来操作线程的方法定义在Object类中?
      • 这些方法只存在于同步中。
      • 使用这些方法时必须要指定所属的锁,即被哪个锁调用这些方法。
      • 而锁可以是任意对象,所以任意对象调用的方法就定义在Object中。

    线程通讯

    多线程间通讯就是多个线程在操作同一资源,但是操作的动作不同。

    • 为什么要通信
      多线程并发执行的时候,如果需要指定线程等待或者唤醒指定线程,那么就需要通信。比如生产者消费者的问题,生产一个消费一个,生产的时候需要负责消费的进程等待,生产一个后完成后需要唤醒负责消费的线程,同时让自己处于等待,消费的时候负责消费的线程被唤醒,消费完生产的产品后又将等待的生产线程唤醒,然后使自己线程处于等待。这样来回通信,以达到生产一个消费一个的目的。
    • 怎么通信
      在同步代码块中,使用锁对象的wait()方法可以让当前线程等待,直到有其他线程唤醒为止。使用锁对象的notify()方法可以唤醒一个等待的线程,或者notifyAll唤醒所有等待的线程。多线程间通信用sleep很难实现,睡眠时间很难把握。
    • 生产者-消费者问题实现代码

    简单代码测试

    测试二

  • 相关阅读:
    加法原理和乘法原理
    布尔矩阵
    Codeforces Round #603 (Div. 2) A. Sweet Problem
    Codeforces Round #603 (Div. 2) D. Secret Passwords 并查集
    poj1611The Suspects并查集
    poj 2236 Wireless Network 并查集
    求斐波那契数的python语言实现---递归和迭代
    python语言实现阶乘的两种方法---递归和迭代
    栈实现二进制转十进制
    栈的基本操作
  • 原文地址:https://www.cnblogs.com/pycrab/p/9740340.html
Copyright © 2011-2022 走看看