天天看点

synchronized&volatile&synchronized原理synchronized&volatile&synchronized原理

synchronized&volatile&synchronized原理

Synchronized&volatile

  • Synchronized本质上是解决对共享变量的访问顺序问题。
  • 多线程环境下,方法内的变量是线程安全的。
  • 多个线程同时处理一个实例,这个实例内的变量是不安全的。
  • 多个线程注入同一个类的不同实例,实例中的变量是安全的。
  • Synchronized获取到的锁是对象锁,当多个线程访问同一个对象时,哪个线程先执行带Synchronized关键字的方- 法,那个线程就持有该方法所属对象的锁,其他线程是等待状态
  • 调用关键字Synchronized声明的方法一定是排队运行的,另外,只有共享资源的访问才需要同步化
  • 一个对象内有多个Synchronized方法/块时
    • A线程先持有object对象的锁,B线程可以以异步的方式调用object对象的非Synchronized方法
    • A线程先持有object对象的锁,B线程如果此时调用object对象中的任一Synchronized方法,则需要等待A线- 程释放锁,也就是同步
  • Synchronized不能继承,举个例子,父类方法被Synchronized修饰,子类重写父类的该方法,但并不带Synchron- ized修饰,就不会产生同步的效果
  • Synchronized代码块,肯定要比Synchronized方法性能要好一些(代码块是直接执行,而方法是间接执行),也就是说Synchronized后面大括号包裹的代码越少越好。当一个线程在执行Synchronized代码块时,其他线程可以执行这个代码块外部的代码。
  • 任意对象都可以作为对象监视器,可以Synchronized(任意对象),优点,如果一个类中有很多个Synchronized方法,这时虽然能够实现同步,但会受到阻塞,影响运行效率;但如果使用同步代码块锁非this对象,则Synchronized(非this)代码块的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,可大大提高运行效率
    • 当多个线程同时执行Synchronized(X,非this)同步代码块时呈同步效果
    • 当其他线程执行X对象中Synchronized同步方法时也呈同步效果
    • 当其他线程执行X对象方法里面的Synchronized(this)时也呈同步效果
  • Synchronized可以用在static关键字之上,表示对当前类持锁,class锁。class锁可以对类的所有对象实例其作用。也就是说,对于class A来说,即便多个线程中每个线程创建一个A的实例,只要他们调用的是A中static synchronized的方法(或者static方法中的synchronized代码块),就得等着,就是同步。
  • 通常情况下,都不会将String作为锁对象,因为String存在一个常量池,当两个线程调用的String相等时,相当于是竞争同一个锁;另外,当锁住某个String变量时x,若同步方法内对x进行了更改,相当于锁对象就变化了,其他再来竞争锁就能竞争到,就起不到同步的效果了(需要注意的是,只要对象不变,即便对象的属性发生变更,也是同步的,String是因为其池化导致的一系列不同)
  • 当一个synchronized方法中出现死循环时(永远都不会释放锁),其他调用改对象中synchronized方法的线程均会一直等下去,解决方法是尽量每个方法锁不同的对象,而不是都锁this
  • 死锁,一个方法,先锁A,等一会再锁B;另一个方法,先锁B,等一会再锁A;当两个方法同时执行时,就出现死锁了。
  • volatile的主要作用是使变量在多个线程间可见。原理是强制线程从公共堆栈中获得变量的值,而不是从线程的私有堆栈中取值。(举个例子:当JVM设置为-server模式时,为了线程运行的效率,线程一直在私有对战中取值,这样,在一个while(x)的循环中,就算外部将x设置为false,线程内也不会跟着改变,从而进入到死循环状态,这时就需要用到volatile关键字了。volatile主要使用的场合是在多个线程中,可以感知实例变量被改变了,并且获得最新的值使用)
  • volatile本质上解决的是私有堆栈和公共堆栈中的值不同步。
  • volatile和synchronized的异同
    • volatile是线程同步的轻量级实现,性能要比synchronized好;volatile只能修饰变量,而synchronized可以修饰法方法或者代码块,synchronized还有性能提升的空间
    • 使用volatile不会产生阻塞,而使用synchronized会产生阻塞
    • volatile保证可见性,但不保证原子性;synchronized保证原子性,间接保证可见性(一个变量只能在同步操作完成了,才能被其他线程操作,所以叫间接)
    • volatile解决的是变量在多个线程之间的可见性 ,而synchronized解决的是多个线程访问资源的同步性
  • synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法或者某一个代码块,它包含两个特性:互斥性和可见性。不仅可以解决一个线程看到对象处于不一致状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护之前的所有的修改结果。

Synchronized原理

  • jdk1.6之后对synchronized锁进行了优化,包含偏向锁、轻量级锁、重量级锁。了解Synchronized的原理我们需要明白三个问题:
    • synchronized是如何实现锁的
    • 为什么任何一个对象都可以成为锁
    • 锁存在哪个地方?
  • Java 对象头是实现synchronized的锁对象的基础,一般而言,synchronized使用的锁对象是存储在java对象头中。它是实现轻量级锁和重量级锁的关键
  • 对象头中含有Mark Word,Mark Word用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、偏向线程id、偏向时间戳等。所以对象头是存储锁的位置。
  • 当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果存在,表示线程已经获得了锁,否则需要再测试Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。执行同步块,这个时候线程2也来访问同步块,也是会检查对象头的mark word里是否存储着当前线程2的偏向锁,发现不是,那么他会进入CAS替换,但是此时替换会失败,因为此时线程1已经替换了。替换失败会进入撤销偏向锁,首先会去暂停拥有了偏向锁的线程1,进入无锁状态。偏向锁在存在竞争的情况下就会去升级成轻量级锁。
    • CAS(自旋锁):自旋锁就是让不满足条件的线程等待一段时间,而不是立即挂起。看持有锁的线程是否能够很快释放锁。怎么自旋呢?其实就是一段没有任何意义的循环。虽然它通过占用处理器的时间来避免线程切换带来的开销,但是如果持有锁的线程不能很快释放锁,那么自旋的线程就会浪费处理器的资源,因为它不会做任何有意义的工作。所以自旋等待的时间或者次数是有一个限度的,如果自旋超过了定义的时间仍然没有获取到锁,则线程应该被挂起。JDK1.6中-XX:+UseSpinning开启; -XX:PreBlockSpin=10 为自旋次数; JDK1.7后,去掉此参数,由jvm控制;
  • 在代码进入同步块的时候,如果同步对象锁状态为无锁状态,虚拟机首先将在当前线程的栈帧中建立一个名为锁记录的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为Displaced Mark Word。这个时候JVM会尝试使用CAS将Mark Word更新为指向栈帧中的锁记录的空间指针。并且把锁标志为设置为00(轻量级锁标志),于此同时如果有另外一个线程2也来进行CAS修改Mark Word,那么将会失败,因为线程1已经获取到该锁,然后线程2将会进行CAS操作不断的去尝试获取锁,这个时候将会引起锁膨胀,就会升级为重量级锁,设置标志位为10
  • 由轻量级锁切换到重量级锁,是发生在轻量级锁释放锁的奇迹,之前在获取锁的时候它拷贝了锁对象头的mark word,在释放锁的时候如果它发现在它持有锁的期间有其他线程来尝试获取锁了,并且该线程对mark word做了修改,两者对比发现不一致,则切换到重量级锁。轻量级解锁时,会使用原子的CAS操作来将Displaced Mark Word替换回到对象头,如果成功,则表示同步过程已完成。如果失败,表示有其他线程尝试获取该锁,则要在释放锁的同时唤醒被挂起的线程进入等待。
  • 重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。主要是,当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗CPU。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。这就是为什么重量级线程开销很大。
    • monitor:我们可以把它理解成一个同步工具,也可以描述为一种同步机制。所有的Java对象都是天生的Monitor,每个object的对象里 markOop->monitor() 里可以保存ObjectMonitor的对象。