面试官:类中两个方法加同步锁,多线程能同时访问吗?

大家好,我是奕叔😈,一个工作八年快退休的程序员,微信:lyfinal,交个朋友,一起交流Java后端技术,相互成长,成为更优秀的🐒~

锁是一个非常难并且复杂的问题,需要计算机底层熟悉才能更好的理解。

  • 首先,同步锁有两种,JVM的synchronized和JDK的ReentrantLock;
  • 然后,多个线程访问这个类的两个方法也有不同的形式,例如访问这个类的两个方法是通过同一个类的实例对象来访问还是通过不同的类的实例对象访问;
  • 再者,一个类的两个方法加了同步锁,这两个被同步方法也没有说明是什么样的方法。他可能是类的普通实例方法,也可能是类中Runnable对象的run方法。

一.synchronized

1.多个线程同时访问同一个类实例对象的两个同步方法:

java 复制代码
package synchronizedTest;
public class Example1
{
    private int num = 0;
    (省略getter.setter, 后同)
    public synchronized void method1()
    {
        System.out.println("同步方法1进入");
        for(int i = 0; i < 10; i++)
        {
            System.out.print("同步方法1:" + num + "--");
            num++;
        }
        System.out.println("同步方法1结束");
    }
    public synchronized void method2()
    {
        System.out.println("method2进入:");
        for(int i = 0; i < 10; i++)
        {
            System.out.print("method2:" + num + "--");
            num++;
        }
        System.out.println("method2结束");
    }
    public static void main(String[] args)
    {
        final Example1 example1 = new Example1();
        Thread thread1 = new Thread(new Runnable()
        {@
            Override
            public void run()
            {
                example1.method1();
            }
        });
        Thread thread2 = new Thread(new Runnable()
        {@
            Override
            public void run()
            {
                example1.method2();
            }
        });
        try
        {
            thread2.join();
            thread1.join();
            thread1.start();
            thread2.start();
        }
        catch(InterruptedException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

输出结果:

java 复制代码
method1进入
同步方法1:0--同步方法1:1--同步方法1:2--同步方法1:3--同步方法1:4--同步方法1:5--同步方法1:6--同步方法1:7--同步方法1:8--同步方法1:9--method1结束
method2进入:
method2:10--method2:11--method2:12--method2:13--method2:14--method2:15--method2:16--method2:17--method2:18--method2:19--method2结束

显然此时多个线程是不能访问同个类(的一个实例对象)的两个同步方法的

2.多个线程同时访问同一个类的不同实例对象的两个同步方法:

将上面的代码稍作修改,主函数中多new一个该类实例

java 复制代码
final&nbsp;Example1&nbsp;example2&nbsp;=&nbsp;new&nbsp;Example1()&nbsp;;

再修改thread2的run方法调用的类实例为example2

java 复制代码
Thread thread2 = new Thread(new Runnable() {@
    Override
    public void run() {
        example2.method2();
    }
});

得到结果:

java 复制代码
同步方法1进入
method2进入:
method2:0--method2:1--同步方法1:0--同步方法1:1--同步方法1:2--method2:2--同步方法1:3--method2:3--同步方法1:4--method2:4--同步方法1:5--method2:5--同步方法1:6--同步方法1:7--method2:6--同步方法1:8--同步方法1:9--method2:7--同步方法1结束
method2:8--method2:9--method2结束

小结:这是因为synchronized是对象锁,即线程获得的锁是施加在一个实例对象上的,如果不同的线程访问的是同一对象上的不同的同步方法,那么显然不能同时进行。

如果是不同对象上的不同的同步方法,那么就是可以同时进行的。

3.多个线程同时访问同一个类实例对象的两个Runnable对象的run方法:

java 复制代码
package synchronizedTest;
public class Example2 {
    private int num;
    public Runnable runnable1 = new Runnable() {@
        Override
        public void run() {
            //同步锁
            synchronized(this) {
                System.out.println("线程1进入");
                for(int i = 0; i < 10; i++) {
                    System.out.print("线程1:" + num + "--");
                }
                System.out.println("线程1结束");
            }
        }
    };
    public Runnable runnable2 = new Runnable() {@
        Override
        public void run() {
            //同步锁
            synchronized(this) {
                System.out.println("thread2进入");
                for(int i = 0; i < 10; i++) {
                    System.out.print("thread2:" + num + "--");
                }
                System.out.println("thread2结束");
            }
        }
    };
    public static void main(String[] args) {
        Example2 example = new Example2(); //创建一个对象
        new Thread(example.runnable1).start(); //同步方法1
        new Thread(example.runnable2).start(); //同步方法2
    }
}

输出结果:

java 复制代码
thread2进入
线程1进入
thread2:0--线程1:0--线程1:0--thread2:0--线程1:0--线程1:0--线程1:0--thread2:0--线程1:0--thread2:0--thread2:0--线程1:0--thread2:0--线程1:0--thread2:0--thread2:0--线程1:0--thread2:0--线程1:0--thread2:0--线程1结束
thread2结束

可见此时多个线程是能同时访问同个类的两个同步方法的。这是因为synchronized(this){ //... }中锁住的不是代码块,即这个锁在run方法中,但是并不是同步了这个run方法,而是括号中的对象this。

也就是说,多个线程会拿到各自的锁,就能够同时执行run方法。(在run方法前声明synchronized也是同样的效果)

java 复制代码
new&nbsp;Thread(example.runnable1).start();&nbsp;//同步方法1
new&nbsp;Thread(example.runnable2).start();&nbsp;//同步方法2

打印出这个this对象,是两个不同的类实例对象:

java 复制代码
synchronizedTest.Example2$1@65db6dfasynchronizedTest.Example2$2@471fab

也说明了不同线程的实例对象不同,都是各自对象的锁,不可以认为是类似于例子1中的同一实例对象,而应该类似与例子2的不同类的实例对象

总结:分析synchronized同步锁的核心在于他是个对象锁,找清楚锁的对象。

相关推荐
马里奥Mario2 小时前
电商系统商品三四级页接口性能优化记录存档
后端
华农第一蒟蒻2 小时前
谈谈跨域问题
java·后端·nginx·安全·okhttp·c5全栈
绝无仅有3 小时前
面试复盘:哔哩哔哩、蔚来、字节跳动、小红书面试与总结
后端·面试·github
绝无仅有3 小时前
面试经历分享:从特斯拉到联影医疗的历程
后端·面试·github
IT_陈寒3 小时前
JavaScript性能优化:这7个V8引擎技巧让我的应用速度提升了50%
前端·人工智能·后端
Tony Bai9 小时前
【Go开发者的数据库设计之道】07 诊断篇:SQL 性能诊断与问题排查
开发语言·数据库·后端·sql·golang
花花鱼10 小时前
spring boot项目使用tomcat发布,也可以使用Undertow(理论)
spring boot·后端·tomcat
你的人类朋友11 小时前
快速搭建redis环境并使用redis客户端进行连接测试
前端·redis·后端
2351612 小时前
【MySQL】数据库事务深度解析:从四大特性到隔离级别的实现逻辑
java·数据库·后端·mysql·java-ee