java synchronized 递归_Synchronized 详解

为了方便记忆,将锁做如下的分类

一、对象锁

包括方法锁(默认锁对象为this,当前实例对象)和同步代码块锁(自己指定锁对象)

1.代码块形式:手动指定锁定对象,也可是是this,也可以是自定义的锁

public class SynchronizedObjectLock implementsRunnable {static SynchronizedObjectLock instence = newSynchronizedObjectLock();@Overridepublic voidrun() {//同步代码块形式——锁为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 voidmain(String[] args) {

Thread t1= newThread(instence);

Thread t2= newThread(instence);

t1.start();

t2.start();

}

}

输出结果:

我是线程Thread-0

Thread-0结束

我是线程Thread-1

Thread-1结束

public class SynchronizedObjectLock implementsRunnable {static SynchronizedObjectLock instence = newSynchronizedObjectLock();//创建2把锁

Object block1 = newObject();

Object block2= newObject();

@Overridepublic voidrun() {//这个代码块使用的是第一把锁,当他释放后,后面的代码块由于使用的是第二把锁,因此可以马上执行

synchronized(block1) {

System.out.println("blocl1锁,我是线程" +Thread.currentThread().getName());try{

Thread.sleep(3000);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println("blocl1锁,"+Thread.currentThread().getName() + "结束");

}synchronized(block2) {

System.out.println("blocl2锁,我是线程" +Thread.currentThread().getName());try{

Thread.sleep(3000);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println("blocl2锁,"+Thread.currentThread().getName() + "结束");

}

}public static voidmain(String[] args) {

Thread t1= newThread(instence);

Thread t2= newThread(instence);

t1.start();

t2.start();

}

输出结果:

blocl1锁,我是线程Thread-0

blocl1锁,Thread-0结束

blocl2锁,我是线程Thread-0  // 可以看到当第一个线程在执行完第一段同步代码块之后,第二个同步代码块可以马上得到执行,因为他们使用的锁不是同一把

blocl1锁,我是线程Thread-1

blocl2锁,Thread-0结束

blocl1锁,Thread-1结束

blocl2锁,我是线程Thread-1

blocl2锁,Thread-1结束

2.方法锁形式:synchronized修饰普通方法,锁对象默认为this

public class SynchronizedObjectLock implementsRunnable {static SynchronizedObjectLock instence = newSynchronizedObjectLock();

@Overridepublic voidrun() {

method();

}public synchronized voidmethod() {

System.out.println("我是线程" +Thread.currentThread().getName());try{

Thread.sleep(3000);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+ "结束");

}public static voidmain(String[] args) {

Thread t1= newThread(instence);

Thread t2= newThread(instence);

t1.start();

t2.start();

}

}

输出结果:

我是线程Thread-0

Thread-0结束

我是线程Thread-1

Thread-1结束

二、类锁

指synchronize修饰静态的方法或指定锁对象为Class对象

1.synchronize修饰静态方法

public class SynchronizedObjectLock implementsRunnable {static SynchronizedObjectLock instence1 = newSynchronizedObjectLock();static SynchronizedObjectLock instence2 = newSynchronizedObjectLock();

@Overridepublic voidrun() {

method();

}//synchronized用在普通方法上,默认的锁就是this,当前实例

public synchronized voidmethod() {

System.out.println("我是线程" +Thread.currentThread().getName());try{

Thread.sleep(3000);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+ "结束");

}public static voidmain(String[] args) {//t1和t2对应的this是两个不同的实例,所以代码不会串行

Thread t1 = newThread(instence1);

Thread t2= newThread(instence2);

t1.start();

t2.start();

}

}

输出结果:

我是线程Thread-0

我是线程Thread-1

Thread-1结束

Thread-0结束

public class SynchronizedObjectLock implementsRunnable {static SynchronizedObjectLock instence1 = newSynchronizedObjectLock();static SynchronizedObjectLock instence2 = newSynchronizedObjectLock();

@Overridepublic voidrun() {

method();

}//synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把

public staticsynchronized voidmethod() {

System.out.println("我是线程" +Thread.currentThread().getName());try{

Thread.sleep(3000);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+ "结束");

}public static voidmain(String[] args) {

Thread t1 = newThread(instence1);

Thread t2= newThread(instence2);

t1.start();

t2.start();

}

}

输出结果:

我是线程Thread-0

Thread-0结束

我是线程Thread-1

Thread-1结束

2.synchronized指定锁对象为Class对象

public class SynchronizedObjectLock implementsRunnable {static SynchronizedObjectLock instence1 = newSynchronizedObjectLock();static SynchronizedObjectLock instence2 = newSynchronizedObjectLock();

@Overridepublic voidrun() {//所有线程需要的锁都是同一把

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 voidmain(String[] args) {

Thread t1= newThread(instence1);

Thread t2= newThread(instence2);

t1.start();

t2.start();

}

}

输出结果:

我是线程Thread-0

Thread-0结束

我是线程Thread-1

Thread-1结束

三、思考

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

2.两个线程同时访问2个对象的同步方法

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

4.两个线程同时访问同步(被synchronized修饰)和非同步(未被snychronized修饰)方法

5.两个线程同时访问1个对象的不同的普通同步方法

6.两个线程同时访问一个静态的synchronized方法和非静态的synchronized方法

7.方法抛出异常后,会释放锁吗?

核心思想:

1.一把锁只能同时被一个线程获取,没有难道锁的线程只能等待(对应上面的1,5)

2.每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把锁(对应上面的2,3,4,6)

3.synchronized修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁(对应上面的7)

四、synchronized的性质

1.可重入性

概念:指同一个线程外层函数获取到锁之后,内层函数可以直接使用该锁

好处:避免死锁,提升封装性(如果不可重入,假设method1拿到锁之后,在method1中又调用了method2,如果method2没办法使用method1拿到的锁,那method2将一直等待,但是method1由于未执行完毕,又无法释放锁,就导致了死锁,可重入正好避免这这种情况)

粒度:线程而非调用(用3中情况来说明与pthread的区别)

1)情况1:证明同一个方法是可重入的(递归)

public classSynchronizedDemo2 {int a = 0;public static voidmain(String[] args) {newSynchronizedDemo2().method1();

}public synchronized voidmethod1() {

System.out.println("a=" +a);if (a == 0) {

a++;

method1();

}

}

}

输出结果:

a=0

a=1

2)情况2:证明可重入不要求是同一个方法

public classSynchronizedDemo2 {public static voidmain(String[] args) {newSynchronizedDemo2().method1();

}public synchronized voidmethod1() {

System.out.println("method1");

method2();

}public synchronized voidmethod2() {

System.out.println("method2");

}

}

输出结果:

method1

method2

3)情况3:证明可重入不要求是同一个类中

public classSynchronizedDemo2 {public synchronized voidmethod1() {

System.out.println("父类method1");

}

}class SubClass extendsSynchronizedDemo2 {public synchronized voidmethod1() {

System.out.println("子类method1");super.method1();

}public static voidmain(String[] args) {newSubClass().method1();

}

}

输出结果:

子类method1

父类method1

2.不可中断性

概念:如果这个锁被B线程获取,如果A线程想要获取这把锁,只能选择等待或者阻塞,直到B线程释放这把锁,如果B线程一直不释放这把锁,那么A线程将一直等待。

相比之下,未来的Lock类,可以拥有中断的能力(如果一个线程等待锁的时间太长了,有权利中断当前已经获取锁的线程的执行,也可以退出等待)

五、深入原理

1.加锁和释放锁的原理:现象、时机(内置锁this)、深入JVM看字节码(反编译看monitor指令)

Lock lock = newReentrantLock();public synchronized voidmethod1() {

System.out.println("synchronized method1");

}public voidmethod2() {

lock.lock();try{

System.out.println("lock method2");

}finally{

lock.unlock();

}

}

method1与method2等价,synchronized相当于先获取锁,执行结束/抛出异常后,释放锁。

深入JVM看字节码,创建如下的代码:

public classSynchronizedDemo2 {

Object object= newObject();public voidmethod1() {synchronized(object) {

}

}

}

使用javac命令进行编译生成.class文件>javac SynchronizedDemo2.java

使用javap命令反编译查看.class文件的信息>javap -verbose SynchronizedDemo2.class得到如下的信息:

java synchronized 递归_Synchronized 详解_第1张图片

关注红色方框里的monitorenter和monitorexit即可。

Monitorenter和Monitorexit指令,会让对象在执行,使其锁计数器加1或者减1。每一个对象在同一时间只与一个monitor(锁)相关联,而一个monitor在同一时间只能被一个线程获得,一个对象在尝试获得与这个对象相关联的Monitor锁的所有权的时候,monitorenter指令会发生如下3中情况之一:

1)monitor计数器为0,意味着目前还没有被获得,那这个线程就会立刻获得然后把锁计数器+1,一旦+1,别的线程再想获取,就需要等待

2)如果这个monitor已经拿到了这个锁的所有权,又重入了这把锁,那锁计数器就会累加,变成2,并且随着重入的次数,会一直累加

3)这把锁已经被别的线程获取了,等待锁释放

monitorexit指令:释放对于monitor的所有权,释放过程很简单,就是讲monitor的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表当前线程不再拥有该monitor的所有权,即释放锁。

2.可重入原理:加锁次数计数器

jvm会负责跟踪对象被加锁的次数

线程第一次获得所,计数器+1,当锁重入的时候,计数器会递增

当任务离开的时候(一个同步代码块的代码执行结束),计数器会减1,当减为0的时候,锁被完全释放。

3.保证可见性的原理:内存模型

访问链接 https://www.cnblogs.com/xyabk/p/10894384.html

六、synchronized的缺陷

效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时

不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活

无法知道是否成功获得锁,相对而言,Lock可以拿到状态,如果成功获取锁,....,如果获取失败,.....

七、Lock对synchronized的弥补

java synchronized 递归_Synchronized 详解_第2张图片

Lock类这里不做过多解释,主要看上面红色方框里面的4个方法

lock():加锁

unlock():解锁

tryLock():尝试获取锁,返回一个boolean值

tryLock(long,TimeUtil):尝试获取锁,可以设置超时

八、注意

1.锁对象不能为空,因为锁的信息都保存在对象头里

2.作用域不宜过大,影响程序执行的速度,控制范围过大,编写代码也容易出错

3.避免死锁

4.在能选择的情况下,既不要用Lock也不要用synchronized关键字,用java.util.concurrent包中的各种各样的类,如果不用该包下的类,在满足业务的情况下,可以使用synchronized关键,因为代码量少,避免出错

九、思考

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

2.Synchronized使得同时只有一个线程可以执行,性能比较差,有什么提升的方法?

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

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

你可能感兴趣的:(java,synchronized,递归)