synchronized的变化①. java5以前,只有Synchronized,这个是操作系统级别的重量级操作,重量级锁,假如锁的竞争比较激烈的话,性能下降②. 在Java早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,挂起线程和恢复线程都需要转入内核态去完成,阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”,时间成本相对较高,这也是为什么早期的synchronized效率低的原因。Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁③. 为什么每一个对象都可以成为一个锁?Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。Monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,成本非常高④. Mutex LockMonitor是在jvm底层实现的,底层代码是c++。本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,状态转换需要耗费很多的处理器时间成本非常高。所以synchronized是Java语言中的一个重量级操作。⑤. Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁,需要有个逐步升级的过程,别一开始就捅到重量级锁⑥. synchronized锁:由对象头中的Mark Word根据锁标志位的不同而被复用及锁升级策略无锁偏向锁单个线程多次访问定义：①. 主要作用:当一段同步代码一直被同一个线程多次访问,由于只有一个线程那么该线程在后续访问时便会自动获得锁(偏向锁)同一个老顾客来访,直接老规矩行方便偏向锁为了解决只有在一个线程执行同步时提高性能②. 64位标记图再看(通过CAS方式修改markword中的线程ID)③. 偏向锁的理论在实际应用运行过程中发现,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁(后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁)。如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步。无需每次加锁解锁都去CAS更新对象头。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。假如不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的④. 技术实现一个synchronized方法被一个线程抢到了锁时,那这个方法所在的对象就会在其所在的Mark Word中将偏向锁修改状态位,同时还会有占用前54位来存储线程指针作为标识。若该线程再次访问同一个synchronized方法时,该线程只需去对象头的Mark Word 中去判断一下是否有偏向锁指向本身的ID,无需再进入Monitor去竞争对象了。⑤. 对于如上的③、④进行细化锁对比偏向锁的操作不用直接捅到操作系统，不涉及用户到内核转换，不必要直接升级为最高级，我们以一个account对象的“对象头”为例，假如有一个线程执行到synchronized代码块的时候，JVMM使用CAS操作把线程指针ID记录到Mark Word当中，并修改标偏向标示，标示当前线程就获得该锁。锁对象变成偏向锁（通过CAS修改对象头里的锁标志位〉，字面意思是“偏向于第一个获得它的线程”的锁。执行完同步代码块后，线程并不会主动释放偏向锁。这时线程获得了锁，可以执行同步代码块。当该线程第二次到达同步代码块时会判断此时持有锁的线程是否还是自己（持有锁的线程ID也在对象头里)，JVM通过account对象的Mark Word判断:当前线程ID还在，说明还持有着这个对象的锁，就可以继续进入临界区工作。由于之前没有释放锁，这里也就不需要重新加锁。如果自始至终使用锁的线程风有一个，很明显偏向锁几乎没有额外开销，性能极高。结论:JVM不用和操作系统协商设置Mutex(争取内核)，它只需要记录下线程ID就标示自己获得了当前锁，不用操作系统接入。上述就是偏向锁:在没有其他线程竞争的时候，一直偏向偏心当前线程，当前线程可以一直执行。实际上偏向锁在JDK1.6之后是默认开启的,但是启动时间有延迟, 4秒所以需要添加参数-XX:BiasedLockingStartupDelay=0,让其在程序启动时立刻启动。开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0关闭偏向锁:关闭之后程序默认会直接进入轻量级锁状态。-XX:-UseBiasedLockingpublic class MyObject{ public static void main(String[] args){ Object o = new Object(); new Thread(() -> { synchronized (o){ System.out.println(ClassLayout.parseInstance(o).toPrintable()); } },"t1").start(); } } //查看状态此时情况下偏向锁4秒后开启偏向锁的撤销偏向锁的撤销(偏向锁使用一种等到竞争出现才释放锁的机制,只有当其他线程竞争锁时,持有偏向锁的原来线程才会被撤销。撤销需要等待全局安全点(该时间点上没有字节码正在执行),同时检查持有偏向锁的线程是否还在执行)第一个线程正在执行synchronized方法(处于同步块),它还没有执行完,其它线程来抢夺,该偏向锁会被取消掉并出现锁升级此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁第一个线程执行完成synchronized方法(退出同步块),则将对象头设置成无锁状态并撤销偏向锁 ,重新偏向(我的理解是,其实如果线程A执行完毕,如果不再去竞争,那么就会重新线程B为偏向锁;如果线程A继续竞争,那么就会CAS自旋 也就升级到了轻量级锁)轻量级锁多线程竞争，但是任意时刻最多只有一个线程竞争，即不存在锁竞争太过激烈的情况，也就没有线程阻塞。有线程来参与锁的竞争,但是获取锁的冲突时间极短（本质就是自旋锁）轻量级锁的获取如果关闭偏向锁,就可以直接进入轻量级锁 -XX:-UseBiasedLocking自旋达到一定次数和程度没有获取则会升级锁java6之前(了解):默认启用,默认情况下自旋的次数是10次,-XX:PreBlockSpin=10来修改或者自旋线程数超过cpu核数一半Java6之后:自适应(自适应意味着自旋的次数不是固定不变的),而是根据:同一个锁上一次自旋的时间和拥有锁线程的状态来决定。重锁①. 有大量的线程参与锁的竞争,冲突性很高②. 锁标志位锁升级后，hashcode值无锁地址在31位偏向锁没有，需要获取的时候需要锁升级。当一个对象已经计算过identity hash code 它就无法进入到偏向锁状态时跳过偏向锁，直接生成轻量级锁。偏向锁过程中遇到一致性哈希计算请求，立马撤销偏向模式，膨胀为重量级锁锁的优缺点synchronized锁升级过程总结:一句话,就是先自旋,不行再阻塞。 实际上是把之前的悲观锁(重量级锁)变成在一定条件下使用偏向锁以及使用轻量级(自旋锁CAS)的形式synchronized在修饰方法和代码块在字节码上实现方式有很大差异,但是内部实现还是基于对象头的MarkWord来实现的JDK1.6之前synchronized使用的是重量级锁,JDK1.6之后进行了优化,拥有了无锁->偏向锁->轻量级锁->重量级锁的升级过程,而不是无论什么情况都使用重量级锁。. 偏向锁、轻量级锁、重量级锁总结偏向锁:适用于单线程适用的情况,在不存在锁竞争的时候进入同步方法/代码块则使用偏向锁。轻量级锁:适用于竞争较不激烈的情况(这和乐观锁的使用范围类似), 存在竞争时升级为轻量级锁,轻量级锁采用的是自旋锁,如果同步方法/代码块执行时间很短的话,采用轻量级锁虽然会占用cpu资源但是相对比使用重量级锁还是更高效。重量级锁:适用于竞争激烈的情况,如果同步方法/代码块执行时间很长,那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁更严重,这时候就需要升级为重量级锁锁消除/ 锁粗化锁消除:从JIT角度看相当于无视它,synchronized (o)不存在了,这个锁对象并没有被共用扩散到其它线程使用,极端的说就是根本没有加这个锁对象的底层机器码,消除了锁的使用** * 锁消除 * 从JIT角度看相当于无视它,synchronized (o)不存在了,这个锁对象并没有被共用扩散到其它线程使用, * 极端的说就是根本没有加这个锁对象的底层机器码,消除了锁的使用 */ public class LockClearUPDemo{ static Object objectLock = new Object();//正常的 public void m1(){ //锁消除,JIT会无视它,synchronized(对象锁)不存在了。不正常的 Object o = new Object(); synchronized (o){ System.out.println("-----hello LockClearUPDemo"+"\t"+o.hashCode()+"\t"+objectLock.hashCode()); } } public static void main(String[] args){ LockClearUPDemo demo = new LockClearUPDemo(); for (int i = 1; i <=10; i++) { new Thread(() -> { demo.m1(); },String.valueOf(i)).start(); } } } /** * 锁粗化 * 假如方法中首尾相接,前后相邻的都是同一个锁对象,那JIT编译器就会把这几个synchronized块合并成一个大块, * 加粗加大范围,一次申请锁使用即可,避免次次的申请和释放锁,提升了性能 */ public class LockBigDemo { static Object objectLock = new Object(); public static void main(String[] args) { new Thread(() -> { synchronized (objectLock) { System.out.println("11111"); } synchronized (objectLock) { System.out.println("22222"); } synchronized (objectLock) { System.out.println("33333"); } },"a").start(); new Thread(() -> { synchronized (objectLock) { System.out.println("44444"); } synchronized (objectLock) { System.out.println("55555"); } synchronized (objectLock) { System.out.println("66666"); } },"b").start(); } }
乐观锁和悲观锁1、 悲观锁(synchronized关键字和Lock的实现类都是悲观锁)什么是悲观锁？认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改适合写操作多的场景,先加锁可以保证写操作时数据正确(写操作包括增删改)、显式的锁定之后再操作同步资源synchronized关键字和Lock的实现类都是悲观锁2、 乐观锁概念:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作乐观锁在Java中通过使用无锁编程来实现，最常采用的时CAS算法,Java原子类中的递增操作就通过CAS自旋实现的适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅度提升乐观锁一般有两种实现方式(采用版本号机制、CAS算法实现) //悲观锁的调用方式 public synchronized void m1(){ //加锁后的业务逻辑 } //保证多个线程使用的是同一个lock对象的前提下 ReetrantLock lock=new ReentrantLock(); public void m2(){ lock.lock(); try{ //操作同步资源 }finally{ lock.unlock(); } } //乐观锁的调用方式 //保证多个线程使用的是同一个AtomicInteger private AtomicInteger atomicIntege=new AtomicInteger(); atomicIntege.incrementAndGet();对象锁/类锁【强制】高并发时，同步调用应该去考量锁的性能损耗。能用无锁数据结构，就不要用锁;能锁区块，就不要锁整个方法体﹔能用对象锁，就不要用类锁。说明︰尽可能使加锁的代码块工作量尽可能的小，避免在锁代码块中调用RPC方法。作用于实例方法，当前实例加锁，进入同步代码前要获得当前实例的锁;作用于代码块，对括号里配置的对象加锁。作用于静态方法，当前类加锁，进去同步代码前要获得当前类对象的锁;//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { phone.sendSMS( ); // phone.hello(); // phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } }*一个对象里面如果有多个synchronized方法，某一个时刻内，只要一个线程去调归其中的一个synchronized方法了，*其它的线程都只能等待，换句话说，某一个时刻内，只能有唯一的一个线程去访问这些synchronized方法*锁的是当前对象this，被锁定后，其它的线程都不能进入到当前对象的其它的synchronized方法//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { // phone.sendSMS( ); phone.hello(); // phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } } -----hello -----sendEmail//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { // phone.sendSMS( ); // phone.hello(); phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } } -----sendSMS -----sendEmail*加个普通方法后发现和同步锁无关*换成两个对象后，不是同一把锁了，情况立刻变化。//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { phone.sendSMS( ); // phone.hello(); // phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public static synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public static synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } } -----sendEmail -----sendSMS//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { // phone.sendSMS( ); // phone.hello(); phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public static synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public static synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } } -----sendEmail -----sendSMS**都换成静态同步方法后，情况又变化三种synchronized 锁的内容有一些差别:*对于普通同步方法，锁的是当前实例对象，通常指this,具体的一部部手机,所有的普通同步方法用的都是同一把锁—>实例对象本身，*对于静态同步方法，锁的是当前类的Class对象，如Phone.class唯一的一个模板。 Phone phone = new Phone(); Phone phone2 = new Phone(); //对于静态同步方法 看的是当前的类 比竟：static关键字修饰的方法，又叫类方法.属于类的，不属于对象， 在实例化对象之前就可以通过类名.方法名调用静态方法。 public static synchronized void sendEmail(){}; //static synchronized 是属于类锁 public synchronized void sendsMs(){}; //synchronize 是属于对象锁 //静态方法static（类方法）和 非静态方法（实例方法）的区别： //方法我们主要分为三种： //1.构造方法 //2.非静态方法（普通方法/实例方法） //3.静态方法（类方法） //一、静态方法和非静态方法的区别（调用对象、引用变量不同） //静态方法:是使用static关键字修饰的方法，又叫类方法.属于类的，不属于对象， 在实例化对象之前就可以通过类名.方法名调用静态方法。 （静态属性，静态方法都是属于类的，可以直接通过类名调用）。 //A.在静态方法中，可以调用静态方法。 //B.在静态方法中，不能调用非静态方法。 //C.在静态方法中，可以引用类变量（即，static修饰的变量）。 //D.在静态方法中，不能引用成员变量（即，没有static修饰的变量）。 //E.在静态方法中，不能使用super和this关键字 //非静态方法:是不含有static关键字修饰的普通方法，又称为实例方法，成员方法。属于对象的，不属于类的。（成员属性，成员方法是属于对象的，必须通过new关键字创建对象后，再通过对象调用）。 //A.在普通方法中，可以调用普通方法。 //B.在普通方法中，可以调用静态方法 //C.在普通方法中，可以引用类变量和成员变量 //D.在普通方法中，可以使用super和this关键字 //二、静态方法和非静态方法的区别（调用方法不同） //静态方法可以直接调用，类名调用和对象调用。（类名.方法名 / 对象名.方法名） //但是非静态方法只能通过对象调用。（对象名.方法名） //三、静态方法和非静态方法的区别（生命周期不同） //静态方法的生命周期跟相应的类一样长，静态方法和静态变量会随着类的定义而被分配和装载入内存中。一直到线程结束，静态属性和方法才会被销毁。（也就是静态方法属于类） //非静态方法的生命周期和类的实例化对象一样长，只有当类实例化了一个对象，非静态方法才会被创建，而当这个对象被销毁时，非静态方法也马上被销毁。（也就是非静态方法属于对象） //总结：类方法可以直接通过类名调用，实例方法必需先实例化类，再初始化对象，然后通过类的实例对象才能调用*对于同步方法块，锁的是synchronized 括号内的对象//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { phone.sendSMS( ); // phone.hello(); // phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public static synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } } -----sendSMS -----sendEmail //入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { // phone.sendSMS( ); // phone.hello(); phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public static synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } } -----sendSMS -----sendEmail**当一个线程试图访问同步代码时它首先必须得到锁，正常退出或抛出异常时必须释放锁。*所有的普通同步方法用的都是同一把锁—实例对象本身，就是new出来的具体实例对象本身,本类this*也就是说如果一个实例对象的普通同步方法获取锁后，该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁。*所有的静态同步方法用的也是同一把锁—类对象本身，就是我们说过的唯一模板class*具体实例对象this和唯一模板class，这两把锁是两个不同的对象，所以静态同步方法与普通同步方法之间是不会有竞态条件的*但是一旦一个静态同步方获取锁后，其他的静态同步方法都必须等待该方法释放锁后才能获取锁。公平锁和非公平锁公平锁：是指多个线程按照申请锁的顺序来获取锁类似排队打饭先来后到非公平锁：是指在多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁,在高并发的情况下,有可能造成优先级反转或者饥饿现象注意：synchronized 和 ReentrantLock 默认是非公平锁为什么会有公平锁和非公平锁1恢复挂起的线程到真正锁的获取还是有时间差的，从开发人员来看这个时间微乎其微，但是从CPU的角度来看，这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU的时间片，尽量减少CPU空闲状态时间。2使用多线程很重要的考量点是线程切换的开销，当采用非公平锁时，当1个线程请求锁获取同步状态，然后释放同步状态，所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大，所以就减少了线程的开销。可重入锁又名递归锁是指在同一个线程在外层方法获取锁的时候，再进入该线程的内层方法会自动获取锁(前提，锁对象得是同一个对象)，不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁，可重入锁的一个优点是可一定程度避免死锁。显示锁/隐式锁隐式锁（即synchronized关键字使用的锁）默认是可重入锁显式锁（即Lock）也有ReentrantLock这样的可重入锁。new Thread(-> { lock.lock(); try{ system.out.println(Thread.currentThread().getName()+"\t ----come in外层调用"); lock.lock(); try{ system.out.println(Thread.currentThread().getName()+"\t ---come in内层调用"); }finally { lock.unLock( ); } }finally { lock. unlock(); } },name: "t1").start(); //加锁和释放锁次数一致，程序运行正常！ new Thread(-> { lock.lock(); try{ system.out.println(Thread.currentThread().getName()+"\t ----come in外层调用"); lock.lock(); try{ system.out.println(Thread.currentThread().getName()+"\t ---come in内层调用"); }finally { lock.unLock( ); } }finally { //由于加锁次数和释放次数不一样，第二个线程始终无法获取到锁，导致一直在等待。 //lock. unlock(); } },name: "t1").start(); new Thread(() -> { lock. lock(); try{ system.out.println(Thread.currentThread().getName()+"\t ----come in外层调用"); }finally { lock.unlock(); } }, name: "t2" ).start(); //加锁和释放锁次数不一致，线程t2不会执行，程序一直等待！死锁死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去，如果系统资源充足，进程的资源请求都能够得到满足，死锁出现的可能性就很低，否则就会因争夺有限的资源而陷入死锁。线程死锁描述的是这样一种情况：多个线程同时被阻塞，它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞，因此程序不可能正常终止。如下图所示，线程 A 持有资源 2，线程 B 持有资源 1，他们同时都想申请对方的资源，所以这两个线程就会互相等待而进入死锁状态。public class DeadLockDemo { private static Object resource1 = new Object();//资源 1 private static Object resource2 = new Object();//资源 2 public static void main(String[] args) { new Thread(() -> { synchronized (resource1) { System.out.println(Thread.currentThread() + "get resource1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "waiting get resource2"); synchronized (resource2) { System.out.println(Thread.currentThread() + "get resource2"); } } }, "线程 1").start(); new Thread(() -> { synchronized (resource2) { System.out.println(Thread.currentThread() + "get resource2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "waiting get resource1"); synchronized (resource1) { System.out.println(Thread.currentThread() + "get resource1"); } } }, "线程 2").start(); } }小结指针指向monitor对象（也称为管程或监视器锁）的起始地址。每个对象都存在着一个monitor与之关联，当一个 monior被某个线程持有后，它便处于锁定状态。在Java虚拟机(HotSpot)中，monitor是由ObjectMonitor实现的，其主要数据结构如下〈位于HotSpot虚拟机源码 ObjectMonitor.hpp文件，C++实现的)