Java高并发syncronized深刻理解

1.Synchronized的做用java

  可以保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。程序员

2.地位:编程

  1)Synchronizedjava关键字,并java的怨言原生支持安全

   2)最基础的互斥同步手段多线程

   3)并发编程中的元老级角色,是并发编程的必学内容并发

3.不使用并发手段会有什么后果?jvm

  (1)两个线程同时a++,最后结果会比预想的少函数

缘由:count++其实是有3个操做完成:性能

  1)读取count;优化

  2)count加一;

  3)count的值写入到内存中。

4.Synchronized的两个用法:

  (1)对象锁:包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(本身指定锁对象)

  (2)类锁:指synchronized修饰静态的方法或指定锁为Class对象

5.synchronized类锁

    概念:Java类可能有不少个对象,但只有一个Class对象,类锁时Class对象的锁(类锁只能在同一时刻一个对象拥有)

     形式:

            1)synchronized加在static方法上;

            2)synchronized*.class)代码块。

6.多线程访问同步方法的7种状况:

  1)两个线程同时访问一个对象的同步方法

  解释:对象所。会相互等待,只能有一个线程持有锁。

  2) 两个线程访问的是两个对象的同步方法

  解释:对象锁。不一样的对象实例,拥有不一样的对象锁,互不影响并发执行

  3) 两个线程访问的是synchronized静态方法

  解释:类锁。

  4) 同时访问同步方法与非同步方法

  解释:synchronized关键字只做用于当前方法,不会影响其余未加关键字的方法的并发行为。所以非同步方法不受到影响,仍是会并发执行

  5) 访问同一个对象的不一样的普通同步方法

  解释:对象锁。

  synchronized关键字虽然没有指定所要的那个锁对象,可是本质上是指定了this这个对象做为它的锁。因此对于同一个实例来说,两个方法拿到的是同一把锁,所以会出现串行的状况。

  6) 同时访问静态synchronized和非静态synchronized方法

  解释:前者为类锁,锁为Class类;后者为对象锁,锁为this对象。所以二者的锁不一样,会并行执行

  7) 方法抛异常后,会释放锁

  特殊:Lock加锁时,若是出现异常,不显式手动释放锁的话,Lock是不会释放的。

  而synchronized不一样,一旦出现异常,会自动释放锁。

  也就是说当第二个线程等待一个被synchronized修饰的方法时,若第一个线程出现异常退出,这把锁会马上释放而且被第二个线程所获取到。JVM会自动把锁释放。

  8扩展:线程进入到一个被synchronized修饰的方法,而在这个方法里面调用了另一个没有被synchronized修饰的方法,这个时候仍是线程安全的吗?
  答案:不是的。出了本方法后,因为另外的方法没有被synchronized修饰,因此说这个方法能够被多个线程同时访问的。

7.synchronized核心思想总结

  1)一把锁同时只能被一个线程获取,没有拿到锁的线程只能等待(对应1,5);

  2)每一个实例对应本身的一把锁,不一样实例对应不一样的锁,相互不影响,能够并行。例外:若是锁是*.class以及synchronized修饰的是static方法时,即类锁时,全部对象共用一把锁(对应2,3,4,6)

  3)不管是正常执行仍是抛出异常,都会释放锁(对应7)

8.syscronized性质(可重入,不可中断)

  1可重入:一个线程拿到了锁,这个线程能够再次使用该锁对其余方法,说明该锁是能够重入的

  2不可重入:一个线程拿到锁了,若是须要再次使用该锁,必须先释放该锁才能再次获取

可重入锁的好处:

  1)避免死锁 2)提高封装性

粒度

  可重入的特性是线程级别的,不是调用级别的(pthread线程)。

问题:为何synchronized具备可重入性?

  答:指的是同一线程的外层函数得到锁以后,内层函数能够直接再次获取该锁(可避免死锁,锁方法1在内部访问锁方法2,用的是同一把锁)。

什么样的可重入?

  1)同一个方法是可重入的;

  2)可重入不要求是同一个方法;

  3)可重入不要求是同一个类中的。

synchronized的性质:不可中断性质

  1)线程A拿到锁,不释放的话,别人永远拿不到锁,永远等待;

  2)Lock锁会有一些比较灵活的功能,按时间等。

加锁和释放锁的原理:

现象

  每一个类的实例对应着一把锁,每一个syncronized方法首先必须得到调用该方法实例的锁,才能执行;不然,线程只能被阻塞。方法一旦执行,便独占了该把锁。直到该方法执行结束返回或者抛出异常,才将该锁释放。锁释放以后,其余阻塞锁才能竞争获取该把锁。

  当一个对象中有synchronized修饰的方法或者代码块的时候,要想执行这段代码,就必须先得到这个对象锁,若是此对象的对象锁已经被其余调用者所占用,就必须等待它被释放。全部的Java对象都含有一个互斥锁,这个锁由JVM自动去获取和释放,咱们只须要指定这个对象就好了,至于锁的释放和获取不 须要咱们操心。

获取和释放锁的时机:内置锁(监视器锁)

  线程在进入同步代码块以前,会自动获取该锁,而且退出代码块时会自动释放该锁。不管是正常退出或者抛出异常退出,都会释放锁。

  然而获取锁的惟一途径:进入这个锁保护的同步代码块或者同步方法中。

Jvm字节码:

  1)Java文件编程为 .class文件:javac xxx.java

  2)经过反编译查看字节码,javap -verbose xxx.class;

  3)synchronized如何实现的,有个加锁monitorenter和解锁monitorexit读到该指令,会让monitor计数器+1-1

注意点:线程既能够在方法完成以后退出,也能够在抛出异常后退出,所以monitorexit数量多于monitorenter。

可重入原理:(加锁次数计数器)

  1)jvm负责跟踪对象被加锁的次数。

  2)线程第一次给对象加锁的时候,计数变为1.每当这个相同线程在此对象上再次得到锁时,计数会递增。

  3)每当任务离开时,计数递减,当计数为0时,锁被彻底释放。

  (1)可重入:若是线程已拿到锁以后,还想再次进入由这把锁所控制的方法中,而无需提早释放,能够直接进入。

  (2)可重入:指的是同一线程的外层函数得到锁以后,内层函数能够直接再次获取该锁。也叫作递归锁。Java中两大递归锁:Synchronized和ReentrantLock。

可见性原理:java内存模型

线程A向线程B发送数据的两个步骤:

  1)线程A修改了本地内存A,并将其存储到主内存中。

  2)线程B再从主内存中读取出来。

  这个过程是由JMMJava Memory Model)控制的。JMM经过控制主内存与每一个线程的本地内存的交互来为Java程序员提供内存可见性的保证。

synchronized是如何作到内存可见性的实现?

  一旦一个代码块或者方法被synchronized修饰以后,那么它在执行完毕以后被锁住的对象所作的任何修改都要在释放锁以前从线程内存写回到主内存 中。因此下一个线程从主内存中读取到的数据必定是最新的。就是经过这样的原理,synchronized关键字保证了每一次执行都是可靠的,保证了可见性

9.Synchronized的缺陷

  1)效率低:

  锁的释放状况少试图得到锁时不能设定超时不能中断一个正在试图得到锁的线程

  2)不够灵活(读写锁更灵活:读操做的时候不会加锁,写操做的时候才会加锁):

  加锁和释放的时机单一每一个锁仅有单一的条件(某个对象),多是不够的

  3没法知道是否成功获取到锁

可是,lock有一些不同的特性:

  Lock能够尝试成功了作一些逻辑判断,若是没有成功作另一些逻辑判断.

Lock类:

  lock.lock();lock.unlock();

  经过这两个方法,能够手动加锁和解锁。

  lock.tryLock();lock.tryLock(10, TimeUnit.MINUTES);

  能够判断是否加锁,返回类型为boolean

补充重点:

1.synchronized的使用注意点:

  锁对象不能为空:锁的信息保存在对象头里面做用域不宜过大:synchronized关键字包裹的范围。

  不须要串行工做的状况下,用并行的方式能够提升运行的效率避免死锁

2.如何选择Lock和synchronized关键字?

  1)若是能够的状况下,二者都不要选择,而是使用java.util.concurrent包中的各类各样的类,例如:CountDownLatch等。使用这些类,不须要本身作同步工做,更方便,也更不容易出错。

  2)若是synchronized关键字在程序中适用,就优先实用这个关键字。由于这样能够减小须要编写的代码,就减小了出错的概率。

  3)若是特别须要Lock这样结构独有的特性的时候,才使用Lock

  以上三点主要是基于减小代码出错为出发点。

10.思考题

  1多个线程等待同一个synchronized锁的时候,JVM如何选择下一个获取锁的是哪一个线程?

  锁调度机制。对于synchronized内置锁,不一样版本的JVM处理方式不一样,blockedrunning都有概率

  2synchronized使得同时只有一个线程能够执行,性能较差,有什么办法能够提高性能?

  (1)优化使用范围,让加锁区在业务容许的状况下足够小。

  (2)用其余类型的锁,例如读写锁,这样在读的时候就不止一个线程能够同时进入代码。

  3)我想更灵活地控制锁的获取和释放(如今释放锁的时机都被规定死了),怎么办?

  本身实现一个Lock

  4)什么是锁的升级、降级?什么是JVM里的偏斜锁、轻量级锁、重量级锁?

  在以前的JVM版本中,synchronized性能不是特别的好,而通过不断的迭代,synchronized性能已经获得了显著的提升,这里面运用的技术就是偏斜锁、轻量级锁、重量级锁。JVM会根据synchronized关键字使用到的次数或者其余的种种指标对锁进行有效的优化使得性能获得大幅上涨,这里面还涉及到了对象头里面的字段。