简介
本文将介绍8种同步方法的访问场景,我们来看看这8种情况下,多线程访问同步方法是否还是线程安全的。这些场景是多线程编程中经常遇到的,而且也是面试时高频被问到的问题,所以不管是理论还是实践,这些都是多线程场景必须要掌握的场景。
8个场景
接下来,我们来通过代码实现,分别判断以下场景是不是线程安全的,以及原因是什么。
-
两个线程同时访问同一个对象的同步方法
-
两个线程同时访问两个对象的同步方法
-
两个线程同时访问(一个或两个)对象的静态同步方法
-
两个线程分别同时访问(一个或两个)对象的同步方法和非同步方法
-
两个线程访问同一个对象中的同步方法,同步方法又调用一个非同步方法
-
两个线程同时访问同一个对象的不同的同步方法
-
两个线程分别同时访问静态synchronized和非静态synchronized方法
-
同步方法抛出异常后,JVM会自动释放锁的情况
场景一:两个线程同时访问同一个对象的同步方法
分析:这种情况是经典的对象锁中的方法锁,两个线程争夺同一个对象锁,所以会相互等待,是线程安全的。
「两个线程同时访问同一个对象的同步方法,是线程安全的。」
场景二:个线程同时访问两个对象的同步方法
这种场景就是对象锁失效的场景,原因出在访问的是两个对象的同步方法,那么这两个线程分别持有的两个线程的锁,所以是互相不会受限的。加锁的目的是为了让多个线程竞争同一把锁,而这种情况多个线程之间不再竞争同一把锁,而是分别持有一把锁,所以我们的结论是:
「两个线程同时访问两个对象的同步方法,是线程不安全的。」
代码验证:
public class Condition2 implements Runnable {
// 创建两个不同的对象
static Condition2 instance1 = new Condition2();
static Condition2 instance2 = new Condition2();
@Override
public void run() {
method();
}
private synchronized void method() {
System.out.println("线程名:" + Thread.currentThread().getName() + ",运行开始");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + ",运行结束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("测试结束");
}
}
运行结果:
两个线程是并行执行的,所以线程不安全。
线程名:Thread-0,运行开始
线程名:Thread-1,运行开始
线程:Thread-0,运行结束
线程:Thread-1,运行结束
测试结束
代码分析:
「问题在此:」
两个线程(thread1、thread2),访问两个对象(instance1、instance2)的同步方法(method()),两个线程都有各自的锁,不能形成两个线程竞争一把锁的局势,所以这时,synchronized修饰的方法method()和不用synchronized修饰的效果一样(不信去把synchronized关键字去掉,运行结果一样),所以此时的method()只是个普通方法。
「如何解决这个问题:」
若要使锁生效,只需将method()方法用static修饰,这样就形成了类锁,多个实例(instance1、instance2)共同竞争一把类锁,就可以使两个线程串行执行了。这也就是下一个场景要讲的内容。
场景三:两个线程同时访问(一个或两个)对象的静态同步方法
这个场景解决的是场景二中出现的线程不安全问题,即用类锁实现:
「两个线程同时访问(一个或两个)对象的静态同步方法,是线程安全的。」
场景四:两个线程分别同时访问(一个或两个)对象的同步方法和非同步方法
这个场景是两个线程其中一个访问同步方法,另一个访问非同步方法,此时程序会不会串行执行呢,也就是说是不是线程安全的