前言
最近针对互联网公司面试问到的知识点,总结出了Java程序员面试涉及到的绝大部分面试题及答案分享给大家,希望能帮助到你面试前的复习且找到一个好的工作,也节省你在网上搜索资料的时间来学习。
内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、SpringBoot、SpringCloud、RabbitMQ、Kafka、Linux等技术栈。
完整版Java面试题地址:JAVA后端面试题整合
八种使用场景:
接下来,我们来通过代码实现,分别判断以下场景是不是线程安全的,以及原因是什么。
1、两个线程同时访问同一个对象的同步方法
2、两个线程同时访问两个对象的同步方法
3、两个线程同时访问(一个或两个)对象的静态同步方法
4、两个线程分别同时访问(一个或两个)对象的同步方法和非同步方法
5、两个线程访问同一个对象中的同步方法,同步方法又调用一个非同步方法
6、两个线程同时访问同一个对象的不同的同步方法
7、两个线程分别同时访问静态synchronized和非静态synchronized方法
8、同步方法抛出异常后,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)共同竞争一把类锁,就可以使两个线程串行执行了。这也就是下一个场景要讲的内容。
场景三:两个线程同时访问(一个或两个)对象的静态同步方法
这个场景解决的是场景二中出现的线程不安全问题,即用类锁实现:
两个线程同时访问(一个或两个)对象的静态同步方法,是线程安全的。