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很难实现,睡眠时间很难把握。
    • 生产者-消费者问题实现代码

    简单代码测试

    测试二

  • 相关阅读:
    Building Java Projects with Gradle
    Vert.x简介
    Spring及Spring Boot 国内快速开发框架
    dip vs di vs ioc
    Tools (StExBar vs Cmder)which can switch to command line window on context menu in windows OS
    SSO的定义、原理、组件及应用
    ModSecurity is an open source, cross-platform web application firewall (WAF) module.
    TDD中测试替身学习总结
    Spring事务银行转账示例
    台式机(华硕主板)前面板音频接口(耳机和麦克风)均无声的解决办法
  • 原文地址:https://www.cnblogs.com/pycrab/p/9740340.html
Copyright © 2011-2022 走看看