synchronized详解

前言

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

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

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

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

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

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

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

synchronized主要方式

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

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

synchronized使用

作用于实例方法

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

public class AccountingSync implements Runnable{//共享资源(临界资源)static int i=0;/*** synchronized 修饰实例方法*/public synchronized void increase(){i++;}@Overridepublic 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中的线程同步锁可以是任意对象

这里我们还需要意识到,当一个线程正在访问一个对象的 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++;}@Overridepublic 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++;}@Overridepublic 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;@Overridepublic void run() {//省略其他耗时操作....//使用同步代码块对变量i进行同步操作,锁对象为instancesynchronized(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底层语义原理

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

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

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

其中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) {}}
}

从字节码中可知同步语句块的实现使用的是monitorenter 和 monitorexit 指令。这也是添Synchronized关键字之后独有的。执行同步代码块首先要先执行monitorenter指令,退出的时候是monitorexit指令。使用Synchronized进行同步,其关键就是必须要对对象的监视器monitor进行获取,当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。Synchronized先天具有重入性。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor (关于重入性稍后会分析),重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。值得注意的是编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。从字节码中也可以看出多了一个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: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack=3, locals=1, args_size=10: aload_01: dup2: getfield      #2                  // Field i:I5: iconst_16: iadd7: putfield      #2                  // Field i:I10: returnLineNumberTable:line 9: 0line 10: 10LocalVariableTable:Start  Length  Slot  Name   Signature0      11     0  this   Lcn/javanode/concurrent/key/synchronizedDesc/SyncMethod;
}

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

Java虚拟机对synchronized的优化

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

无锁

可以看到mark word 里面此时存了

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

偏向锁

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

轻量级锁

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

自旋锁

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

锁消除

消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间,如下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;@Overridepublic 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内存屏障

  • 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();@Overridepublic 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();@Overridepublic 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();@Overridepublic 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();@Overridepublic 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();@Overridepublic 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();@Overridepublic 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,谢谢!有任何不妥的地方望纠正

1202年最新最详细最全的synchronized知识详解相关推荐

  1. 最全 VxLAN 知识详解

    什么是VXLAN VXLAN(Virtual eXtensible Local Area Network,虚拟扩展局域网),是由IETF定义的NVO3(Network Virtualization o ...

  2. centos7中ps显示的内容_值得收藏,史上最全Linux ps命令详解

    原标题:值得收藏,史上最全Linux ps命令详解 一.程序员的疑惑 大概在十多年前,我当时还是一个产品经理.由于一些工作的原因,需要向运维工程师学习一些linux常用命令. 当使用linux ps这 ...

  3. 尼尔机器人技能快捷键_尼尔机械纪元武器配置及全出招操作详解-游侠网

    看着游戏女主华丽的招式是不是很羡慕呢,其实只要掌握了一些出招操作,你也可以做到,那么怎么操作呢?小编这就给大家分享下尼尔机械纪元武器配置及全出招操作详解,一起看下吧. 武器配置1: 轻型刀装备于轻栏位 ...

  4. 史上最全的IP地址详解,速来get

    史上最全的IP地址详解 白在了解虚拟机网络之前,我们首先得了解ip地址是什么? 1.简单局域网的构成 局域网:一般称为内网 简单局域网的构成:交换机.网线.PC(其他IT终端) 交换机:用来组建内网的 ...

  5. java塔防兽人种族,《魔兽世界》部落种族大全 全种族资料+种族特长详解

    魔兽世界种族有哪些?游戏中共有三大部落分别是联盟.部落和同盟种族,各大部落又有不同种族,小编这里给大家带来了魔兽世界全种族资料+种族特长详解,一起来看下吧. 魔兽世界全种族资料+种族特长详解魔兽世界部 ...

  6. 【网络安全】BGP线路是什么意思?最全BGP路由协议技术详解

    [网络安全]BGP线路是什么意思?最全BGP路由协议技术详解 一.BGP 的基本概念 自治系统AS(Autonomous System) AS 是指在一个实体管辖下的拥有相同选路策略的 IP 网络.B ...

  7. iptable 详解_最全的iptables防火墙详解.pdf

    最全的iptables防火墙详解 iptables / iptables / iippttaabblleess官方网站:hhttttpp::nneettffiilltteerr..oorrgg// • ...

  8. 猛兽之地服务器维护,猛兽之地Roguelands全材料获取途径详解

    以下就是小编为大家带来的猛兽之地Roguelands全材料获取途径详解: 蘑菇镇主要产出: 发光蘑菇 屎壳螂 少量怪兽抓 古老废墟主要产出: 甲壳碎片 辛辣种子 星空岩 折磨大陆主要产出: 星之果 少 ...

  9. 摩克机器人_乐克乐克2全素材入手方法详解

    在乐克乐克2手游中素材究竟该怎么获得呢?在乐克乐克2手游中素材可以说是不少小伙伴们都关心的问题了,那么想了解的小伙伴们下面就来看一下吧~! 乐克乐克2全素材入手方法详解 [荆棘的刺].[捷拉卡之木], ...

最新文章

  1. Java学习笔记二十五:Java面向对象的三大特性之多态
  2. 卷积神经网络如何处理一维时间序列数据?
  3. Janusec应用安全网关(WAF网关)
  4. Unable to open a test connection to the given database.
  5. java xmlsec_找不到net.shibboleth.tool:xmlsectool:2.0.0
  6. redis的基本使用笔记二
  7. dede config.chche.inc.php,dede/config.php · 辉辉菜/三强源码 - Gitee.com
  8. 学习笔记(11):Python网络编程并发编程-粘包底层原理分析
  9. SQL Server 全文索引的管理
  10. MyBatis 基础知识
  11. java 数组或者链表输出
  12. excel工具栏隐藏了怎么办_Pixel Studio 像素软件教程之工具栏介绍
  13. 计算机java二级 netbeans 安装使用方法
  14. elastic-Job配置参数详细解释
  15. 手机投屏到电脑完美教程,安卓和苹果用户均可使用
  16. 用简单的Python HTTP server来共享文件
  17. MTK芯片资料下载,MT6580处理器参数介绍
  18. 怎么让背景铺满整个页面_css新手教程之背景图充满整个屏幕
  19. 渠道分析 之 渠道分析的价值 -3
  20. dellr服务器性能测试软件,戴尔Poweredge R740服务器使用感受 | 小迪的生产力工具室...

热门文章

  1. 企业网站 源码 服务邮箱:_口碑营销:乌海腾讯企业邮箱服务报价
  2. Java黑皮书课后题第8章:*8.2(求矩阵对角线元素的和)使用下面的方法头编写一个方法,求n*n的double类型矩阵中主对角线上所有数字的和。编写一个程序,读取一个4*4的矩阵,显示主对角线和
  3. 抓wifi包工具linux,测试工具之在Ubuntu使用Wireshark抓无线数据包
  4. 根据树的两种遍历序列求第三种遍历序列
  5. 程序员面试100题之十二:求数组中最长递增子序列
  6. 基于Session的国际化实现
  7. 广州技术沙龙第 4 期报名开始!
  8. 牛客网(剑指offer) 第十五题 反转链表
  9. Apache 创建虚拟主机目录和设置默认访问页面
  10. ⚡关于Eastmount博客「网络安全自学篇」系列重要通知!!!⚡