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

大家好,我是奕叔😈,一个工作八年快退休的程序员,微信: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同步锁的核心在于他是个对象锁,找清楚锁的对象。

相关推荐
无心水40 分钟前
深入Java线程池:BlockingQueue实现全景解析与实战指南
java·后端·面试
Java水解42 分钟前
Rust 性能优化实战:从 unsafe 使用到 SIMD 指令,让服务端响应快 2 倍
后端·rust
Java水解44 分钟前
JAVA面试题大全(200+道题目)
java·后端·面试
卷福同学1 小时前
AI浏览器comet拉新,一单20美元(附详细教程)
人工智能·后端
大鱼七成饱1 小时前
掌握 anyhow,让你的 Rust 错误处理优雅又安全
后端·rust
2301_772093561 小时前
高并发webserver_interview
运维·服务器·数据库·后端·网络协议·mysql·wireshark
HashTang2 小时前
不用再配服务器了!这套 Next.js + Cloudflare 模板,一个人搞定全栈出海
前端·后端·边缘计算
水淹萌龙3 小时前
玩转 Go 表达式引擎:expr 实战指南
开发语言·后端·golang
Yeats_Liao4 小时前
Go Web 编程快速入门 07.4 - 模板(4):组合模板与逻辑控制
开发语言·后端·golang
咖啡教室5 小时前
每日一个计算机小知识:MAC地址
后端·网络协议