Synchronized 同步方法的八种使用场景

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Synchronized 同步方法的八种使用场景相关的知识,希望对你有一定的参考价值。

参考技术A

分析:这种情况是经典的对象锁中的方法锁,两个线程争夺同一个对象锁,所以会相互等待,是线程安全的。

两个线程同时访问同一个对象的同步方法,是线程安全的。

这种场景就是对象锁失效的场景,原因出在访问的是两个对象的同步方法,那么这两个线程分别持有的两个线程的锁,所以是互相不会受限的。加锁的目的是为了让多个线程竞争同一把锁,而这种情况多个线程之间不再竞争同一把锁,而是分别持有一把锁,所以我们的结论是:

两个线程同时访问两个对象的同步方法,是线程不安全的。

运行结果:
两个线程是并行执行的,所以线程不安全。

线程名:Thread-0,运行开始
线程名:Thread-1,运行开始
线程:Thread-0,运行结束
线程:Thread-1,运行结束
测试结束

两个线程(thread1、thread2),访问两个对象(instance1、instance2)的同步方法(method()),两个线程都有各自的锁,不能形成两个线程竞争一把锁的局势,所以这时,synchronized修饰的方法method()和不用synchronized修饰的效果一样(不信去把synchronized关键字去掉,运行结果一样),所以此时的method()只是个普通方法。

若要使锁生效,只需将method()方法用static修饰,这样就形成了类锁,多个实例(instance1、instance2)共同竞争一把类锁,就可以使两个线程串行执行了。这也就是下一个场景要讲的内容。

这个场景解决的是场景二中出现的线程不安全问题,即用类锁实现:

两个线程同时访问(一个或两个)对象的静态同步方法,是线程安全的。

这个场景是两个线程其中一个访问同步方法,另一个访问非同步方法,此时程序会不会串行执行呢,也就是说是不是线程安全的呢?
我们可以确定是线程不安全的,如果方法不加synchronized都是安全的,那就不需要同步方法了。验证下我们的结论:

两个线程分别同时访问(一个或两个)对象的同步方法和非同步方法,是线程不安全的。

两个线程是并行执行的,所以是线程不安去的。

线程名:Thread-0,同步方法,运行开始
线程名:Thread-1,普通方法,运行开始
线程:Thread-0,同步方法,运行结束
线程:Thread-1,普通方法,运行结束
测试结束

问题在于此:method1没有被synchronized修饰,所以不会受到锁的影响。即便是在同一个对象中,当然在多个实例中,更不会被锁影响了。结论:

非同步方法不受其它由synchronized修饰的同步方法影响

你可能想到一个类似场景:多个线程访问同一个对象中的同步方法,同步方法又调用一个非同步方法,这个场景会是线程安全的吗?

我们来实验下这个场景,用两个线程调用同步方法,在同步方法中调用普通方法;再用一个线程直接调用普通方法,看看是否是线程安全的?

线程名:Thread-0,普通方法,运行开始
线程名:Thread-1,同步方法,运行开始
线程:Thread-1,同步方法,运行结束,开始调用普通方法
线程名:Thread-1,普通方法,运行开始
线程:Thread-0,普通方法,运行结束
线程:Thread-1,普通方法,运行结束
线程名:Thread-2,同步方法,运行开始
线程:Thread-2,同步方法,运行结束,开始调用普通方法
线程名:Thread-2,普通方法,运行开始
线程:Thread-2,普通方法,运行结束
测试结束

我们可以看出,普通方法被两个线程并行执行,不是线程安全的。这是为什么呢?

因为如果非同步方法,有任何其他线程直接调用,而不是仅在调用同步方法时,才调用非同步方法,此时会出现多个线程并行执行非同步方法的情况,线程就不安全了。

对于同步方法中调用非同步方法时,要想保证线程安全,就必须保证非同步方法的入口,仅出现在同步方法中。但这种控制方式不够优雅,若被不明情况的人直接调用非同步方法,就会导致原有的线程同步不再安全。所以不推荐大家在项目中这样使用,但我们要理解这种情况,并且我们要用语义明确的、让人一看就知道这是同步方法的方式,来处理线程安全的问题。

所以,最简单的方式,是在非同步方法上,也加上synchronized关键字,使其变成一个同步方法,这样就变成了《场景五:两个线程同时访问同一个对象的不同的同步方法》,这种场景下,大家就很清楚的看到,同一个对象中的两个同步方法,不管哪个线程调用,都是线程安全的了。

两个线程访问同一个对象中的同步方法,同步方法又调用一个非同步方法,仅在没有其他线程直接调用非同步方法的情况下,是线程安全的。若有其他线程直接调用非同步方法,则是线程不安全的。

这个场景也是在探讨对象锁的作用范围,对象锁的作用范围是对象中的所有同步方法。所以,当访问同一个对象中的多个同步方法时,结论是:

两个线程同时访问同一个对象的不同的同步方法时,是线程安全的。

是线程安全的。

线程名:Thread-1,同步方法1,运行开始
线程:Thread-1,同步方法1,运行结束
线程名:Thread-0,同步方法0,运行开始
线程:Thread-0,同步方法0,运行结束
测试结束

两个方法(method0()和method1())的synchronized修饰符,虽没有指定锁对象,但默认锁对象为this对象为锁对象,
所以对于同一个实例(instance),两个线程拿到的锁是同一把锁,此时同步方法会串行执行。这也是synchronized关键字的可重入性的一种体现。

线程名:Thread-0,静态同步方法0,运行开始
线程名:Thread-1,非静态同步方法1,运行开始
线程:Thread-1,非静态同步方法1,运行结束
线程:Thread-0,静态同步方法0,运行结束
测试结束

本场景探讨的是synchronized释放锁的场景:

只有当同步方法执行完或执行时抛出异常这两种情况,才会释放锁。

所以,在一个线程的同步方法中出现异常的时候,会释放锁,另一个线程得到锁,继续执行。而不会出现一个线程抛出异常后,另一个线程一直等待获取锁的情况。这是因为JVM在同步方法抛出异常的时候,会自动释放锁对象。

线程名:Thread-0,运行开始
线程名:Thread-0,抛出异常,释放锁
线程名:Thread-1,运行开始
Exception in thread "Thread-0" java.lang.RuntimeException
at com.study.synchronize.conditions.Condition7.method0(Condition7.java:34)
at com.study.synchronize.conditions.Condition7.run(Condition7.java:17)
at java.lang.Thread.run(Thread.java:748)
线程:Thread-1,运行结束
测试结束

可以看出线程还是串行执行的,说明是线程安全的。而且出现异常后,不会造成死锁现象,JVM会自动释放出现异常线程的锁对象,其他线程获取锁继续执行。

使用synchronized的几种场景

1.修饰一个方法
synchronized 修饰一个方法很简单,就是在方法的前面加synchronized,例如:

public synchronized void method()
{
   // todo
}

在定义接口方法时不能使用synchronized关键字。
构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
synchronized 关键字不能被继承 ,如果要同步需要显式的加上关键字。
synchronized 关键字修饰的方法如果被重写默认不同步,如果要同步需要显式的加上关键字,或者super父类的方法也就相当于同步了。

2.修饰代码块

public  void method()
{
   synchronized(this)
   synchronized(XX.class)
}

synchronized(this)锁的是当前对象,当前有几个对象那么这个this就是有多份,这里的this只能锁同一个对象。
synchronized(XX.class)只要是这个类型的class这把锁就都有用

当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
以上规则对其它对象锁同样适用.

3.修饰静态方法
我们知道 静态方法是属于类的而不属于对象的 。同样的, synchronized修饰的静态方法锁定的是这个类的所有对象,所有类用它都会有锁的效果

public synchronized static void method() {
   // todo
}

4.修饰一个类
其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象,只要是这个类型的class不管有几个对象都会起作用。如下代码

class ClassName {
    public void method() {
       synchronized(ClassName.class) {
          // todo
       }
    }
 }

 

总结:

A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
D.同步关键字锁的是对象





















以上是关于Synchronized 同步方法的八种使用场景的主要内容,如果未能解决你的问题,请参考以下文章

单例模式的八种实现

性能测试场景-常见的八种策略

JAVA设计模式——单例模式八种方式

单例模式的八种写法

@Transaction注解失效的八种情况及解决办法

@Transaction注解失效的八种情况及解决办法