Java并发编程—synchronized保证线程安全的原理分析

前言

程安全是并发编程中的重要关注点,应该注意到的是,形成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操做共享数据。所以为了解决这个问题,咱们可能须要这样一个方案,当存在多个线程操做共享数据时,须要保证同一时刻有且只有一个线程在操做共享数据,其余线程必须等到该线程处理完数据后再进行,这种方式有个高尚的名称叫互斥锁,即能达到互斥访问目的的锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其余线程只能处于等待的状态,直到当前线程处理完毕释放该锁。在 Java 中,关键字 synchronized能够保证在同一个时刻,只有一个线程能够执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操做),同时咱们还应该注意到synchronized另一个重要的做用,synchronized可保证一个线程的变化(主要是共享数据的变化)被其余线程所看到(保证可见性,彻底能够替代Volatile功能),这点确实也是很重要的。java

注:文章有点长能够根据目录来看!编程

synchronized的三种应用方式

synchronized关键字最主要有如下3种应用方式,下面分别介绍安全

  • 修饰实例方法,做用于当前实例加锁,进入同步代码前要得到当前实例的锁bash

  • 修饰静态方法,做用于当前类对象加锁,进入同步代码前要得到当前类对象的锁多线程

  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要得到给定对象的锁。并发

synchronized做用于实例方法

所谓的实例对象锁就是用synchronized修饰实例对象中的实例方法,注意是实例方法不包括静态方法,以下app

public class AccountingSync implements Runnable{
    //共享资源(临界资源)
    static int i=0;

    /** * synchronized 修饰实例方法 */
    public synchronized void increase(){
        i++;
    }
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        AccountingSync instance=new AccountingSync();
        Thread t1=new Thread(instance);
        Thread t2=new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
    /** * 输出结果: * 2000000 */
}
复制代码

上述代码中,咱们开启两个线程操做同一个共享资源即变量i,因为i++;操做并不具有原子性,该操做是先读取值,而后写回一个新值,至关于原来的值加上1,分两步完成,若是第二个线程在第一个线程读取旧值和写回新值期间读取i的域值,那么第二个线程就会与第一个线程一块儿看到同一个值,并执行相同值的加1操做,这也就形成了线程安全失败,所以对于increase方法必须使用synchronized修饰,以便保证线程安全。此时咱们应该注意到synchronized修饰的是实例方法increase,在这样的状况下,当前线程的锁即是实例对象instance,注意Java中的线程同步锁能够是任意对象。从代码执行结果来看确实是正确的,假若咱们没有使用synchronized关键字,其最终输出结果就极可能小于2000000,这即是synchronized关键字的做用。这里咱们还须要意识到,当一个线程正在访问一个对象的 synchronized 实例方法,那么其余线程不能访问该对象的其余 synchronized 方法,毕竟一个对象只有一把锁,当一个线程获取了该对象的锁以后,其余线程没法获取该对象的锁,因此没法访问该对象的其余synchronized实例方法,可是其余线程仍是能够访问该实例对象的其余非synchronized方法,固然若是是一个线程 A 须要访问实例对象 obj1 的 synchronized 方法 f1(当前对象锁是obj1),另外一个线程 B 须要访问实例对象 obj2 的 synchronized 方法 f2(当前对象锁是obj2),这样是容许的,由于两个实例对象锁并不一样相同,此时若是两个线程操做数据并不是共享的,线程安全是有保障的,遗憾的是若是两个线程操做的是共享数据,那么线程安全就有可能没法保证了,以下代码将演示出该现象ide

public class AccountingSyncBad implements Runnable{
    static int i=0;
    public synchronized void increase(){
        i++;
    }
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        //new新实例
        Thread t1=new Thread(new AccountingSyncBad());
        //new新实例
        Thread t2=new Thread(new AccountingSyncBad());
        t1.start();
        t2.start();
        //join含义:当前线程A等待thread线程终止以后才能从thread.join()返回
        t1.join();
        t2.join();
        System.out.println(i);
    }
}
复制代码

上述代码与前面不一样的是咱们同时建立了两个新实例AccountingSyncBad,而后启动两个不一样的线程对共享变量i进行操做,但很遗憾操做结果是1452317而不是指望结果2000000,由于上述代码犯了严重的错误,虽然咱们使用synchronized修饰了increase方法,但却new了两个不一样的实例对象,这也就意味着存在着两个不一样的实例对象锁,所以t1和t2都会进入各自的对象锁,也就是说t1和t2线程使用的是不一样的锁,所以线程安全是没法保证的。解决这种困境的的方式是将synchronized做用于静态的increase方法,这样的话,对象锁就当前类对象,因为不管建立多少个实例对象,但对于的类对象拥有只有一个,全部在这样的状况下对象锁就是惟一的。下面咱们看看如何使用将synchronized做用于静态的increase方法。函数

synchronized做用于静态方法

当synchronized做用于静态方法时,其锁就是当前类的class对象锁。因为静态成员不专属于任何一个实例对象,是类成员,所以经过class对象锁能够控制静态 成员的并发操做。须要注意的是若是一个线程A调用一个实例对象的非static synchronized方法,而线程B须要调用这个实例对象所属类的静态 synchronized方法,是容许的,不会发生互斥现象,由于访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁,看以下代码性能

public class AccountingSyncClass implements Runnable{
    static int i=0;

    /** * 做用于静态方法,锁是当前class对象,也就是 * AccountingSyncClass类对应的class对象 */
    public static synchronized void increase(){
        i++;
    }

    /** * 非静态,访问时锁不同不会发生互斥 */
    public synchronized void increase4Obj(){
        i++;
    }

    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        //new新实例
        Thread t1=new Thread(new AccountingSyncClass());
        //new心事了
        Thread t2=new Thread(new AccountingSyncClass());
        //启动线程
        t1.start();t2.start();

        t1.join();t2.join();
        System.out.println(i);
    }
}
复制代码

因为synchronized关键字修饰的是静态increase方法,与修饰实例方法不一样的是,其锁对象是当前类的class对象。注意代码中的increase4Obj方法是实例方法,其对象锁是当前实例对象,若是别的线程调用该方法,将不会产生互斥现象,毕竟锁对象不一样,但咱们应该意识到这种状况下可能会发现线程安全问题(操做了共享静态变量i)。

synchronized同步代码块

除了使用关键字修饰实例方法和静态方法外,还可使用同步代码块,在某些状况下,咱们编写的方法体可能比较大,同时存在一些比较耗时的操做,而须要同步的代码又只有一小部分,若是直接对整个方法进行同步操做,可能会得不偿失,此时咱们可使用同步代码块的方式对须要同步的代码进行包裹,这样就无需对整个方法进行同步操做了,同步代码块的使用示例以下:

public class AccountingSync implements Runnable{
    static AccountingSync instance=new AccountingSync();
    static int i=0;
    @Override
    public void run() {
        //省略其余耗时操做....
        //使用同步代码块对变量i进行同步操做,锁对象为instance
        synchronized(instance){
            for(int j=0;j<1000000;j++){
                    i++;
              }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(instance);
        Thread t2=new Thread(instance);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println(i);
    }
}
复制代码

从代码看出,将synchronized做用于一个给定的实例对象instance,即当前实例对象就是锁对象,每次当线程进入synchronized包裹的代码块时就会要求当前线程持有instance实例对象锁,若是当前有其余线程正持有该对象锁,那么新到的线程就必须等待,这样也就保证了每次只有一个线程执行i++;操做。固然除了instance做为对象外,咱们还可使用this对象(表明当前实例)或者当前类的class对象做为锁,以下代码:

//this,当前实例对象锁
synchronized(this){
    for(int j=0;j<1000000;j++){
        i++;
    }
}

//class对象锁
synchronized(AccountingSync.class){
    for(int j=0;j<1000000;j++){
        i++;
    }
}
复制代码

了解完synchronized的基本含义及其使用方式后,下面咱们将进一步深刻理解synchronized的底层实现原理。

synchronized底层语义原理

Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现, 不管是显式同步(有明确的 monitorenter 和 monitorexit 指令,即同步代码块)仍是隐式同步都是如此。在 Java 语言中,同步用的最多的地方多是被 synchronized 修饰的同步方法。同步方法 并非由 monitorenter 和 monitorexit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的,关于这点,稍后详细分析。下面先来了解一个概念Java对象头,这对深刻理解synchronized实现原理很是关键。

  若是对上面的执行结果还有疑问,也先不用急,咱们先来了解Synchronized的原理,再回头上面的问题就一目了然了。咱们先经过反编译下面的代码来看看Synchronized是如何实现对代码块进行同步的:

public class SynchronizedDemo {
     public void method() {
         synchronized (this) {
            System.out.println("Method 1 start");
         }
     }
 }
复制代码

反编译结果:
image.png

关于这两条指令的做用,咱们直接参考JVM规范中描述:

monitorenter :
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership. 复制代码

这段话的大概意思为:

每一个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的全部权,过程以下:

  1. 若是monitor的进入数为0,则该线程进入monitor,而后将进入数设置为1,该线程即为monitor的全部者。

  2. 若是线程已经占有该monitor,只是从新进入,则进入monitor的进入数加1.

  3. 若是其余线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再从新尝试获取monitor的全部权。

monitorexit:
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
复制代码

这段话的大概意思为:

执行monitorexit的线程必须是objectref所对应的monitor的全部者。

指令执行时,monitor的进入数减1,若是减1后进入数为0,那线程退出monitor,再也不是这个monitor的全部者。其余被这个monitor阻塞的线程能够尝试去获取这个 monitor 的全部权。

  经过这两段描述,咱们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是经过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为何只有在同步的块或者方法中才能调用wait/notify等方法,不然会抛出java.lang.IllegalMonitorStateException的异常的缘由。

咱们再来看一下同步方法的反编译结果:

源代码:

public class SynchronizedMethod {
     public synchronized void method() {
        System.out.println("Hello World!");
    }
 }
复制代码

反编译结果:image.png

从反编译的结果来看,方法的同步并无经过指令monitorenter和monitorexit来完成(理论上其实也能够经过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,若是设置了,执行线程将先获取monitor,获取成功以后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其余任何线程都没法再得到同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需经过字节码来完成。

关于synchronized 可能须要了解的关键点

synchronized的可重入性

从互斥锁的设计上来讲,当一个线程试图操做一个由其余线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求本身持有对象锁的临界资源时,这种状况属于重入锁,请求将会成功,在java中synchronized是基于原子性的内部锁机制,是可重入的,所以在一个线程调用synchronized方法的同时在其方法体内部调用该对象另外一个synchronized方法,也就是说一个线程获得一个对象锁后再次请求该对象锁,是容许的,这就是synchronized的可重入性。以下:

public class AccountingSync implements Runnable{
    static AccountingSync instance=new AccountingSync();
    static int i=0;
    static int j=0;
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){

            //this,当前实例对象锁
            synchronized(this){
                i++;
                increase();//synchronized的可重入性
            }
        }
    }

    public synchronized void increase(){
        j++;
    }


    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(instance);
        Thread t2=new Thread(instance);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println(i);
    }
}
复制代码

正如代码所演示的,在获取当前实例对象锁后进入synchronized代码块执行同步代码,并在代码块中调用了当前实例对象的另一个synchronized方法,再次请求当前实例锁时,将被容许,进而执行方法体代码,这就是重入锁最直接的体现,须要特别注意另一种状况,当子类继承父类时,子类也是能够经过可重入锁调用父类的同步方法。注意因为synchronized是基于monitor实现的,所以每次重入,monitor中的计数器仍会加1。

线程中断与synchronized

线程中断

正如中断二字所表达的意义,在线程运行(run方法)中间打断它,在Java中,提供了如下3个有关线程中断的方法

//中断线程(实例方法)
public void Thread.interrupt();

//判断线程是否被中断(实例方法)
public boolean Thread.isInterrupted();

//判断是否被中断并清除当前中断状态(静态方法)
public static boolean Thread.interrupted();
复制代码

当一个线程处于被阻塞状态或者试图执行一个阻塞操做时,使用Thread.interrupt()方式中断该线程,注意此时将会抛出一个InterruptedException的异常,同时中断状态将会被复位(由中断状态改成非中断状态),以下代码将演示该过程:

public class InterruputSleepThread3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                //while在try中,经过异常中断就能够退出run循环
                try {
                    while (true) {
                        //当前线程处于阻塞状态,异常必须捕捉处理,没法往外抛出
                        TimeUnit.SECONDS.sleep(2);
                    }
                } catch (InterruptedException e) {
                    System.out.println("Interruted When Sleep");
                    boolean interrupt = this.isInterrupted();
                    //中断状态被复位
                    System.out.println("interrupt:"+interrupt);
                }
            }
        };
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        //中断处于阻塞状态的线程
        t1.interrupt();

        /** * 输出结果: Interruted When Sleep interrupt:false */
    }
}
复制代码

如上述代码所示,咱们建立一个线程,并在线程中调用了sleep方法从而使用线程进入阻塞状态,启动线程后,调用线程实例对象的interrupt方法中断阻塞异常,并抛出InterruptedException异常,此时中断状态也将被复位。这里有些人可能会诧异,为何不用Thread.sleep(2000);而是用TimeUnit.SECONDS.sleep(2);其实缘由很简单,前者使用时并无明确的单位说明,然后者很是明确表达秒的单位,事实上后者的内部实现最终仍是调用了Thread.sleep(2000);,但为了编写的代码语义更清晰,建议使用TimeUnit.SECONDS.sleep(2);的方式,注意TimeUnit是个枚举类型。ok~,除了阻塞中断的情景,咱们还可能会遇处处于运行期且非阻塞的状态的线程,这种状况下,直接调用Thread.interrupt()中断线程是不会获得任响应的,以下代码,将没法中断非阻塞状态下的线程:

public class InterruputThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(){
            @Override
            public void run(){
                while(true){
                    System.out.println("未被中断");
                }
            }
        };
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        t1.interrupt();

        /** * 输出结果(无限执行): 未被中断 未被中断 未被中断 ...... */
    }
}
复制代码

虽然咱们调用了interrupt方法,但线程t1并未被中断,由于处于非阻塞状态的线程须要咱们手动进行中断检测并结束程序,改进后代码以下:

public class InterruputThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(){
            @Override
            public void run(){
                while(true){
                    //判断当前线程是否被中断
                    if (this.isInterrupted()){
                        System.out.println("线程中断");
                        break;
                    }
                }

                System.out.println("已跳出循环,线程中断!");
            }
        };
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        t1.interrupt();

        /** * 输出结果: 线程中断 已跳出循环,线程中断! */
    }
}
复制代码

是的,咱们在代码中使用了实例方法isInterrupted判断线程是否已被中断,若是被中断将跳出循环以此结束线程。综合所述,能够简单总结一下中断两种状况,一种是当线程处于阻塞状态或者试图执行一个阻塞操做时,咱们可使用实例方法interrupt()进行线程中断,执行中断操做后将会抛出interruptException异常(该异常必须捕捉没法向外抛出)并将中断状态复位,另一种是当线程处于运行状态时,咱们也可调用实例方法interrupt()进行线程中断,但同时必须手动判断中断状态,并编写中断线程的代码(其实就是结束run方法体的代码)。有时咱们在编码时可能须要兼顾以上两种状况,那么就能够以下编写:

public void run(){
    try {
    //判断当前线程是否已中断,注意interrupted方法是静态的,执行后会对中断状态进行复位
    while (!Thread.interrupted()) {
        TimeUnit.SECONDS.sleep(2);
    }
    } catch (InterruptedException e) {

    }
}
复制代码

中断与synchronized

事实上线程的中断操做对于正在等待获取的锁对象的synchronized方法或者代码块并不起做用,也就是对于synchronized来讲,若是一个线程在等待锁,那么结果只有两种,要么它得到这把锁继续执行,要么它就保存等待,即便调用中断线程的方法,也不会生效。演示代码以下

public class SynchronizedBlocked implements Runnable{

    public synchronized void f() {
        System.out.println("Trying to call f()");
        while(true) // Never releases lock
            Thread.yield();
    }

    /** * 在构造器中建立新线程并启动获取对象锁 */
    public SynchronizedBlocked() {
        //该线程已持有当前实例锁
        new Thread() {
            public void run() {
                f(); // Lock acquired by this thread
            }
        }.start();
    }
    public void run() {
        //中断判断
        while (true) {
            if (Thread.interrupted()) {
                System.out.println("中断线程!!");
                break;
            } else {
                f();
            }
        }
    }


    public static void main(String[] args) throws InterruptedException {
        SynchronizedBlocked sync = new SynchronizedBlocked();
        Thread t = new Thread(sync);
        //启动后调用f()方法,没法获取当前实例锁处于等待状态
        t.start();
        TimeUnit.SECONDS.sleep(1);
        //中断线程,没法生效
        t.interrupt();
    }
}
复制代码

咱们在SynchronizedBlocked构造函数中建立一个新线程并启动获取调用f()获取到当前实例锁,因为SynchronizedBlocked自身也是线程,启动后在其run方法中也调用了f(),但因为对象锁被其余线程占用,致使t线程只能等到锁,此时咱们调用了t.interrupt();但并不能中断线程。

等待唤醒机制与synchronized

所谓等待唤醒机制本篇主要指的是notify/notifyAll和wait方法,在使用这3个方法时,必须处于synchronized代码块或者synchronized方法中,不然就会抛出IllegalMonitorStateException异常,这是由于调用这几个方法前必须拿到当前对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象,在前面的分析中,咱们知道monitor 存在于对象头的Mark Word 中(存储monitor引用指针),而synchronized关键字能够获取 monitor ,这也就是为何notify/notifyAll和wait方法必须在synchronized代码块或者synchronized方法调用的缘由。

synchronized (obj) {
       obj.wait();
       obj.notify();
       obj.notifyAll();         
 }
复制代码

须要特别理解的一点是,与sleep方法不一样的是wait方法调用完成后,线程将被暂停,但wait方法将会释放当前持有的监视器锁(monitor),直到有线程调用notify/notifyAll方法后方能继续执行,而sleep方法只让线程休眠并不释放锁。同时notify/notifyAll方法调用后,并不会立刻释放监视器锁,而是在相应的synchronized(){}/synchronized方法执行结束后才自动释放锁。

Java虚拟机对synchronized的优化

锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁能够从偏向锁升级到轻量级锁,再升级的重量级锁,可是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级,关于重量级锁,前面咱们已详细分析过,下面咱们将介绍偏向锁和轻量级锁以及JVM的其余优化手段,这里并不打算深刻到每一个锁的实现和转换过程更多地是阐述Java虚拟机所提供的每一个锁的核心优化思想,毕竟涉及到具体过程比较繁琐,如需了解详细过程能够查阅《深刻理解Java虚拟机原理》。

偏向锁

偏向锁是Java 6以后加入的新锁,它是一种针对加锁操做的优化手段,通过研究发现,在大多数状况下,锁不只不存在多线程竞争,并且老是由同一线程屡次得到,所以为了减小同一线程获取锁(会涉及到一些CAS操做,耗时)的代价而引入偏向锁。偏向锁的核心思想是,若是一个线程得到了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再作任何同步操做,即获取锁的过程,这样就省去了大量有关锁申请的操做,从而也就提供程序的性能。因此,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续屡次是同一个线程申请相同的锁。可是对于锁竞争比较激烈的场合,偏向锁就失效了,由于这样场合极有可能每次申请锁的线程都是不相同的,所以这种场合下不该该使用偏向锁,不然会得不偿失,须要注意的是,偏向锁失败后,并不会当即膨胀为重量级锁,而是先升级为轻量级锁。下面咱们接着了解轻量级锁。

轻量级锁

假若偏向锁失败,虚拟机并不会当即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6以后加入的),此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁可以提高程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。须要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,若是存在同一时间访问同一锁的场合,就会致使轻量级锁膨胀为重量级锁。

自旋锁

轻量级锁失败后,虚拟机为了不线程真实地在操做系统层面挂起,还会进行一项称为自旋锁的优化手段。这是基于在大多数状况下,线程持有锁的时间都不会太长,若是直接挂起操做系统层面的线程可能会得不偿失,毕竟操做系统实现线程之间的切换时须要从用户态转换到核心态,这个状态之间的转换须要相对比较长的时间,时间成本相对较高,所以自旋锁会假设在不久未来,当前的线程能够得到锁,所以虚拟机会让当前想要获取锁的线程作几个空循环(这也是称为自旋的缘由),通常不会过久,多是50个循环或100循环,在通过若干次循环后,若是获得锁,就顺利进入临界区。若是还不能得到锁,那就会将线程在操做系统层面挂起,这就是自旋锁的优化方式,这种方式确实也是能够提高效率的。最后没办法也就只能升级为重量级锁了。

锁消除

消除锁是虚拟机另一种锁的优化,这种优化更完全,Java虚拟机在JIT编译时(能够简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),经过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,经过这种方式消除没有必要的锁,能够节省毫无心义的请求锁时间,以下StringBuffer的append是一个同步方法,可是在add方法中的StringBuffer属于一个局部变量,而且不会被其余线程所使用,所以StringBuffer不可能存在共享资源竞争的情景,JVM会自动将其锁消除。

重量级锁

即synchronized,一直等待线程施放锁后才能够拿到资源。

相关文章
相关标签/搜索