1202年最新最详细最全的synchronized知识详解

synchronized详解

前言

通俗:形成线程安全问题的主要诱因有两点:html

  • 存在共享数据(也称临界资源)
  • 存在多条线程共同操做共享数据

学术:形成线程安全问题的主要诱因有两点:java

  • 主内存和线程的工做内存而致使的内存可见性问题
  • 重排序致使的问题,须要知道happens-before规则

当存在多个线程操做共享数据时,须要保证同一时刻有且只有一个线程在操做共享数据,其余线程必须等到该线程处理完数据后再进行,这种方式的名称叫·互斥锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其余线程只能处于等待的状态,直到当前线程处理完毕释放该锁。node

关键字 synchronized能够保证(1)在同一个时刻,只有一个线程能够执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操做),(2)synchronized保证一个线程的变化(主要是共享数据的变化)被其余线程所看到(保证可见性,彻底能够替代Volatile功能)也就是happens-before规则segmentfault

标注:在学习中须要修改的内容以及笔记全在这里 www.javanode.cn,谢谢!有任何不妥的地方望纠正数组

synchronized主要方式

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

  • 修饰实例方法,做用于当前实例加锁,进入同步代码前要得到当前实例的锁
  • 修饰静态方法,做用于当前类对象加锁,进入同步代码前要得到当前类对象的锁
  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要得到给定对象的锁

synchronized使用

做用于实例方法

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

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
     */
}

咱们应该注意到synchronized修饰的是实例方法increase,在这样的状况下,当前线程的锁即是实例对象instance,注意Java中的线程同步锁能够是任意对象bash

这里咱们还须要意识到,当一个线程正在访问一个对象的 synchronized 实例方法,那么其余线程不能访问该对象的其余 synchronized 方法,毕竟一个对象只有一把锁,当一个线程获取了该对象的锁以后,其余线程没法获取该对象的锁,因此没法访问该对象的其余synchronized实例方法,可是其余线程仍是能够访问该实例对象的其余非synchronized方法,固然若是是一个线程 A 须要访问实例对象 obj1 的 synchronized 方法 f1(当前对象锁是obj1),另外一个线程 B 须要访问实例对象 obj2 的 synchronized 方法 f2(当前对象锁是obj2),这样是容许的,由于两个实例对象锁并不一样相同,此时若是两个线程操做数据并不是共享的,线程安全是有保障的数据结构

遗憾的是若是两个线程操做的是共享数据,那么线程安全就有可能没法保证了,以下代码将演示出该现象多线程

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做用于静态方法时,其锁就是当前类的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)。

做用于同步代码块

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

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锁对象和锁类是本质都是对对象来加锁。类也是一个特殊的对象。只不过类对象只有一个。对象内锁不一样的属性,两个同步方法能够同时访问

image.png

synchronized底层语义原理

在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。以下:

image.png

对象头由mark word ,指向对象实例数据的指针(Class Metadata Address),length组成,其结构说明以下表:

虚拟机位数 头对象结构 说明
32/64bit Mark Word 存储对象的hashCode、锁信息或分代年龄或GC标志等信息
32/64bit Class Metadata Address 类型指针指向对象的类元数据,JVM经过这个指针肯定该对象是哪一个类的实例。
32/64bit length 当对象是数组时,length保存数组的长度

image.png

其中Mark Word在默认状况下存储着对象的HashCode、分代年龄、锁标记位等如下是32位JVM的Mark Word默认存储结构

锁状态 25bit 4bit 1bit是不是偏向锁 2bit 锁标志位
无锁状态 对象HashCode 对象分代年龄 0 01

因为对象头的信息是与对象自身定义的数据没有关系的额外存储成本,所以考虑到JVM的空间效率,Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象自己的状态复用本身的存储空间,

synchronized代码块底层原理

Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现, 不管是显式同步(有明确的 monitorenter 和 monitorexit 指令,即同步代码块)仍是隐式同步(方法级的同步)都是如此。在 Java 语言中,同步用的最多的地方多是被 synchronized 修饰的同步方法。同步方法 并非由 monitorenter 和 monitorexit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的,

深刻JVM看字节码,建立以下的代码:

public class SynchronizedDemo2 {
    Object object = new Object();
    public void method1() {
        synchronized (object) {

        }
    }
}

image.png

从字节码中可知同步语句块的实现使用的是monitorenter 和 monitorexit 指令。这也是添Synchronized关键字以后独有的。执行同步代码块首先要先执行monitorenter指令,退出的时候是monitorexit指令。使用Synchronized进行同步,其关键就是必需要对对象的监视器monitor进行获取,当执行monitorenter指令时,<u>当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程能够成功取得 monitor,并将计数器值设置为 1,</u>取锁成功。Synchronized先天具备重入性。若是当前线程已经拥有 objectref 的 monitor 的持有权,那它能够重入这个 monitor (关于重入性稍后会分析),重入时计数器的值也会加 1。假若其余线程已经拥有 objectref 的 monitor 的全部权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其余线程将有机会持有 monitor 。值得注意的是编译器将会确保不管方法经过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而不管这个方法是正常结束仍是异常结束。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然能够正确配对执行,<u>编译器会自动产生一个异常处理器,这个异常处理器声明可处理全部的异常,它的目的就是用来执行 monitorexit 指令。</u>从字节码中也能够看出多了一个monitorexit指令,它就是异常结束时被执行的释放monitor 的指令。

synchronized方法底层原理

方法级的同步是隐式,即无需经过字节码指令来控制的,它实如今方法调用和返回操做之中。JVM能够从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,若是设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 而后再执行方法,最后再方法完成(不管是正常完成仍是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其余任何线程都没法再得到同一个monitor。若是一个同步方法执行期间抛出了异常,而且在方法内部没法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法以外时自动释放。

//方法级的同步是隐式,
    public class SyncMethod {

        public int i;

        public synchronized void syncTask(){
            i++;
        }

    }
## 反编译之后的字节码
  public synchronized void syncTask();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field i:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field i:I
        10: return
      LineNumberTable:
        line 9: 0
        line 10: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   Lcn/javanode/concurrent/key/synchronizedDesc/SyncMethod;
}

从字节码中能够看出,<u>synchronized修饰的方法并无monitorenter指令和monitorexit指令,取得代之的确实是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法</u>,JVM经过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。这即是synchronized锁在同步代码块和同步方法上实现的基本原理。同时咱们还必须注意到的是在Java早期版本中,synchronized属于重量级锁,效率低下,由于监视器锁(monitor)是依赖于底层的操做系统的Mutex Lock来实现的,而操做系统实现线程之间的切换时须要从用户态转换到核心态,这个状态之间的转换须要相对比较长的时间,时间成本相对较高,这也是为何早期的synchronized效率低的缘由。庆幸的是在Java 6以后Java官方对从JVM层面对synchronized较大优化,因此如今的synchronized锁效率也优化得很不错了,Java 6以后,为了减小得到锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁。

Java虚拟机对synchronized的优化

锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁能够从偏向锁升级到轻量级锁,再升级的重量级锁,可是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级,

image.png

无锁

能够看到mark word 里面此时存了

  • 锁状态
  • 对象的hashcode
  • 对象的分代年龄,这里用于垃圾回收
  • 是否偏向锁:0否1是
  • 锁标志位:01

偏向锁

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

轻量级锁

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

自旋锁

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

锁消除

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

/**
 * 消除StringBuffer同步锁
 */
public class StringBufferRemoveSync {

    public void add(String str1, String str2) {
        //StringBuffer是线程安全,因为sb只会在append方法中使用,不可能被其余线程引用
        //所以sb属于不可能共享的资源,JVM会自动消除内部的锁
        StringBuffer sb = new StringBuffer();
        sb.append(str1).append(str2);
    }

    public static void main(String[] args) {
        StringBufferRemoveSync rmsync = new StringBufferRemoveSync();
        for (int i = 0; i < 10000000; i++) {
            rmsync.add("abc", "123");
        }
    }

}

锁的优缺点对比

优势 缺点 使用场景
偏向锁 加锁和解锁不须要CAS操做,没有额外的性能消耗,和执行非同步方法相比仅存在纳秒级的差距 若是线程间存在锁竞争,会带来额外的锁撤销的消耗 适用于只有一个线程访问同步快的场景
轻量级锁 竞争的线程不会阻塞,提升了响应速度 如线程成始终得不到锁竞争的线程,使用自旋会消耗CPU性能 追求响应时间,同步快执行速度很是快
重量级锁 线程竞争不适用自旋,不会消耗CPU 线程阻塞,响应时间缓慢,在多线程下,频繁的获取释放锁,会带来巨大的性能消耗 追求吞吐量,同步快执行速度较长

补充知识点

1.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。

2.synchronized实现可见性的原理

简单地说可见性就是把工做内存中的数据刷入主内存,加载数据。具体到内存屏障

int b = 0;

 int c = 0;

 synchronized(this) { -> monitorenter 

    Load内存屏障 
    Acquire内存屏障 

    int a = b; 
    c = 1; => synchronized代码块里面仍是可能会发生指令重排 

    Release内存屏障 
} -> monitorexit 

Store内存屏障

image.png

  • Load屏障的做用是执行refresh处理器缓存的操做,说白了就是对别的处理器更新过来变量,从其余处理器的高速缓存(或者主内存) 加载数据到本身的高速缓存来,确保本身看到的是最新的数据。
  • Store屏障的做用是执行flush处理器缓存的操做,说白了就是把本身当前处理器更新的变量的值,都刷新到高速缓存(或者主内存)里去

基于synchronized代码块字节码层面上来讲:

  • 在moniterenter指令以后,加入了一个load屏障,执行一个refresh操做从其余处理器的高速缓存读取最新数据或者从主内存加载数据
  • 在moniterexit指令以后,加入一个store屏障,执行flush操做,把最新值写入高速缓存或者主内存

3.synchronized实现有序性的原理

如上面代码所示

  • 在monitorenter指令以后,Load屏障以后,会加一个Acquire屏障,这个屏障的做用是禁止读操做和读写操做之间发生指令重排序。
  • 在monitorexit指令以前,会加一个Release屏障,这个屏障的做用是禁止写操做和读写操做之间发生重排序。

因此说,经过 Acquire屏障和Release屏障,就可让synchronzied保证有序性,只有synchronized内部的指令能够重排序,可是绝对 不会跟外部的指令发生重排序。

巩固提高

找了几个例子,巩固一下上面学的,看一下能不能想出来执行顺序呢!

案例一
public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence = new SynchronizedObjectLock();

    @Override
    public void run() {
        // 同步代码块形式——锁为this,两个线程使用的锁是同样的,线程1必需要等到线程0释放了该锁后,才能执行
        synchronized (this) {
            System.out.println("我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence);
        Thread t2 = new Thread(instence);
        t1.start();
        t2.start();
    }
}
 
//    执行结果
/**
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
**/
案例二
public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence = new SynchronizedObjectLock();
    // 建立2把锁
    Object block1 = new Object();
    Object block2 = new Object();

    @Override
    public void run() {
        // 这个代码块使用的是第一把锁,当他释放后,后面的代码块因为使用的是第二把锁,所以能够立刻执行
        synchronized (block1) {
            System.out.println("block1锁,我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("block1锁,"+Thread.currentThread().getName() + "结束");
        }

        synchronized (block2) {
            System.out.println("block2锁,我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);//sleep方法并不会失去锁。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("block2锁,"+Thread.currentThread().getName() + "结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence);
        Thread t2 = new Thread(instence);
        t1.start();
        t2.start();
    }
}
  
//    执行结果
/**
block1锁,我是线程Thread-0
block1锁,Thread-0结束
block2锁,我是线程Thread-0    //能够看到当第一个线程在执行完第一段同步代码块以后,第二个同步代码块能够立刻获得执行,由于他们使用的锁不是同一把
block1锁,我是线程Thread-1
block1锁,Thread-1结束
block2锁,Thread-0结束
block2锁,我是线程Thread-1
block2锁,Thread-1结束
**/
方法锁形式:synchronized修饰普通方法,锁对象默认为this
//当前线程的锁即是实例对象
//当一个线程获取了该对象的锁以后,其余线程没法获取该对象的锁,因此没法访问该对象的其余synchronized实例方法
public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence = new SynchronizedObjectLock();

    @Override
    public void run() {
        method();
    }

    public synchronized void method() {
        System.out.println("我是线程" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence);
        Thread t2 = new Thread(instence);
        t1.start();
        t2.start();
    }
}
  
//    执行结果
/**
我是线程Thread-1
Thread-1结束
我是线程Thread-0
Thread-0结束
**/
方法锁形式:synchronized修饰普通方法,锁对象默认为this
//t1和t2对应的this是两个不一样的实例,持有锁不一样 普通锁只是当前实例
public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        method();
    }

    // synchronized用在普通方法上,默认的锁就是this,当前实例
    public synchronized void method() {
        System.out.println("我是线程" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束");
    }

    public static void main(String[] args) {
        // t1和t2对应的this是两个不一样的实例,因此代码不会串行
        Thread t1 = new Thread(instence1);
        Thread t2 = new Thread(instence2);
        t1.start();
        t2.start();
    }
}
  
//    执行结果
/**
我是线程Thread-0
我是线程Thread-1
Thread-1结束
Thread-0结束
**/
类锁形式
public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        method();
    }

    // synchronized用在静态方法上,默认的锁就是当前所在的Class类,因此不管是哪一个线程访问它,须要的锁都只有一把
    public static synchronized void method() {
        System.out.println("我是线程" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence1);
        Thread t2 = new Thread(instence2);
        t1.start();
        t2.start();
    }
}
//    执行结果
/**
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
**/
同步代码块
public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        // 全部线程须要的锁都是同一把
        synchronized(SynchronizedObjectLock.class){
            System.out.println("我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence1);
        Thread t2 = new Thread(instence2);
        t1.start();
        t2.start();
    }
}
//    执行结果
/**
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
**/

标注:在学习中须要修改的内容以及笔记全在这里 www.javanode.cn,谢谢!有任何不妥的地方望纠正

相关文章
相关标签/搜索