Java教程

Java多线程2---线程同步和异步、线程安全、锁机制

本文主要是介绍Java多线程2---线程同步和异步、线程安全、锁机制,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、多线程同步和异步

1、异步编程模型---侧重并发和吞吐量

          两个线程t1,t2各自执行各自的,t1不管t2,t2不管t1,互不等待,多线程并发,效率较高

2、同步编程模型---侧重安全

         t1执行的时候,必须等t2执行结束,t2执行的时候,必须等t1执行结束,两个线程之间发生等待关系,线程串行排队执行

   异步就是并发,同步就是排队。

二、多线程安全问题

1、发生线程数据安全问题的条件:

(1)线程并发

(2)有共享数据

(3)共享数据有修改行为

2、解决线程安全问题

  (1)互斥同步----synchronized lock

  (2)非阻塞同步---乐观的并发策略,不需要线程挂起 :CAS

  (3)无同步---可重入代码,ThreadLocal

3、涉及的概念

(1)临界区

  •  临界区指的是一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问的特性
  •  当有线程进入临界区段时,其他线程或是进程必须等待

(2)互斥量

  •  互斥量是一个可以处于两态之一的变量:解锁和加锁;
  • 一个二进制位或整型量表示,0表示解锁,而其他所有的值则表示加锁
  • 当一个线程(或进程)需要访问临界区时,它调用mutex_lock
  • 如果该互斥量已经加锁,调用线程被阻塞,直到在临界区中的线程完成并调用mutex_unlock

(3)管程(Monitor 监视器)

  •  一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源
  • 实现了在一个时间点,最多只有一个线程在执行管程的某个子程序

(4)信号量

  • 在多线程环境下使用的一种设施,用来保证两个或多个关键代码段不被并发调用
  • 在进入一个关键代码段之前,线程必须获取一个信号量;
  • 一旦该关键代码段完成了,那么该线程必须释放信号量

(5)CAS(比较并交换)

  •   3个操作数,内存值V,旧的预期值A,要修改的新值B
  • 当且仅当预期值A和内存值V相同时,将内存值V修改为B

        确保本执行过程中内存值没被其他线程修改过

  •  问题:ABA----解决:原子更新带引用版本号

三、解决线程安全问题

(一)互斥同步---synchronized lock

   1、 synchronized

   (1)执行原理

  •  每个Java对象都有一把锁,占有该对象锁,才能对该对象进行操作。
  • 当一个线程遇到synchronized时,则需要占有该对象锁,而其他线程只能在同步代码块外面等待这个线程把同步代码块中的代码块执行结束,归还对象锁,这时才能占有共享对象锁,并进入同步代码块内进行执行。
  •    每个对象的monitor记录对象的状态
  •  可重入 synchronized有两个字节码:monitorenter----获取对象的monitor---加锁---锁计数器+1
  •                                                             monitorexit------获取对象的monitor---解锁---锁计数器-1

   (2)synchronized加锁的三种情况

  •    加在需要同步的代码块之外,方法体内

         synchronized(要锁的共享对象){

           对象相关的同步代码块  

}

【注】方法内要锁的共享对象一般为this,也可为其他全局变量。

           若当前对象是方法的局部变量不可用this。(非共享,线程私有)

  • 加在run方法中

run(){     

synchronized(要锁的共享对象){

           对象相关的同步代码块  

   }

}

【注】此处不可以为this,因为此处this表当前对象--线程对象,一个线程对应一个自己对象,非共享对象

缺点: 扩大了同步范围,同步了整个方法,执行效率降低,同步代码越少,效率越高

  • 加在方法上

 public  synchronized void 方法名(){ 相关同步操作 }

     优点:代码写的少了,节简

       缺点:锁的只能是this当前类对象,不能是其他全局变量,不灵活

   2、lock--ReentrantLock(可重入锁)

   (1)基本操作

          加锁:对象.lock

          解锁:对象.unlock

   (2)与synchronized不同点

  • 等待可中断:在持有锁的线程长时间不释放锁的时候,等待的线程可以选择放弃等待.
  • 公平锁:按照申请锁的顺序来一次获得锁称为公平锁------按申请锁的顺序

                   ReentrantLock可以通过构造函数实现公平锁.  new RenentrantLock(boolean fair)

                   synchronized的是非公平锁-----锁释放后,任何线程都有机会获得锁

  • 锁绑定多个条件:通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能.通过Condition的await(),signal();

                              synchronized----只能用wait和notify实现一个隐含条件

(二)非阻塞同步

  1、原理

  •      基于冲突检测的乐观并发策略
  •     先操作,如果没有其他线程争用共享的数据,操作就成功,

                   如果有,则进行其他的补偿(最常见是不断的重试)

2、这类指令

    1)测试并设置(test-and-set)
    2)获取并增加
    3)交换
    4)比较并交换(CAS)  jdk5后
    5)加载链接/条件储存(Load-Linked/Store-Conditional  LL/SC)

(三)无同步

1、可重入代码(Reentrant Code)---  纯代码

  • 不依赖存储在堆上的数据和公用的系统资源,
  • 用到的状态量都由参数中传入,
  • 不调用非可重入的方法,它的返回结果是可以预测的。

  2、 线程本地存储(Thread Local Storage)

  •    把共享数据的可见范围限制在同一个线程之内,
  •    主要通过java.lang.ThreadLocal类来实现线程本地存储的功能。

          使用ThreadLocalMap存储共享变量值

四、锁机制

1、锁的类型

(1) 悲观锁----排他锁
        在开始改变此对象之前就将该对象锁住,并且直到提交了所做的更改之后才释放锁。

        排队进行

       乐观锁----共享锁
       假设不会发生并发冲突。轻易不加锁。

(2)公平锁---按申请顺序依次获得锁

        非公平锁---通过竞争获得锁

2、Synchronied同步锁的四种状态

(1)无锁状态   

(2)偏向锁状态 01 锁标志位

   <1>目的:大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得

              为了让线程获得锁的代价更低而引入了偏向锁

    <2>特点:锁偏向于第一个获得它的线程,

               当一个线程访问同步快并获取锁时,会在对象头和栈帧中锁记录里存储锁偏向的线程ID

               若执行过程中一直没有被其他线程获取,则持有偏向锁的线程将不需要同步

               一旦有其他线程尝试获取锁,偏向模式立即结束

    <3>适用场景只有一个线程访问同步代码块

(3)轻量级锁状态00

   <1>目的:为了在没有竞争前提下,减少重量级锁适用操作系统互斥量产生的性能消耗(加锁减锁)

   <2>特点:使用CAS尝试将对象的Mark Word更新为锁记录的指针

              成功--线程拥有锁;失败--自旋等待

              当出现两条以上线程争同一个锁,轻量级锁转换成重量级锁
  <3> 适用场景:两个线程

  (4)重量级锁状态10

   <1> 特点:线程竞争不使用自旋,不消耗cpu,但是线程会阻塞,响应时间慢

      至少包含一个竞争锁的队列,和一个信号阻塞队列(wait队列),前者负责做互斥,后一个用于做线程同步

     <2>适用场景:两个以上线程

3、锁优化

(1)自旋锁

  •  线程挂起和恢复的操作都需要转入内核态中完成,这些操作给系统的并发性能带来了很大的压力.
  •  在许多应用中,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得,
  •   可以让后请求锁的线程等待一会儿,但不放弃处理器的执行时间,让线程执行一个忙循环(自旋)。

  •     自旋锁默认的自旋次数值是10次,可以使用参数-XX:PreBlockSpin更改。

(2)自适应自旋
    自适应自旋意味着自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。


(3)锁清除

    虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持。

(4)锁粗化

    如果虚拟机探测到有一系列连续操作都对同一个对象反复加锁和解锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部

(5)锁升级

     偏向锁---->轻量级锁----->重量级锁

参考:java线程安全和锁机制详解_jiangbb8686的博客-CSDN博客

           Java 并发 - 理论基础 | Java 全栈知识体系

这篇关于Java多线程2---线程同步和异步、线程安全、锁机制的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!