举例说明Java中synchronize对象锁和类锁有什么区别?

  • 刚学到这里的时候很困惑;只是简单知道:1、synchronized 加到 static 方法前面是给class 加锁,即类锁;2、而synchronized 加到非静态方法前面是给对象上锁,即对象锁。
  • 下面我就从代码的角度演示一下二者不同的现象。

1、对象锁和类锁是不同的锁,所以多个线程同时执行这2个不同锁的方法时,是异步的。

  • 创建一个类Task2,在其中定义三个方法 doLongTimeTaskA和doLongTimeTaskB是类锁,
  • 而doLongTimeTaskC是对象锁。
  • 再创建3个线程,每个线程中调用调用该类对象锁和类锁的方法。
  • 在main函数中执行abc三个线程的代码。依次调用。
public class Task2 {
        public synchronized static void doLongTimeTaskA() {
            System.out.println("name = " + Thread.currentThread().getName() + ", begain ,"+ new Date());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("name = " + Thread.currentThread().getName() + ", end ,"+ new Date());
        }

        public synchronized static void doLongTimeTaskB() {
            System.out.println("name = " + Thread.currentThread().getName() + ", begain ,"+ new Date());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("name = " + Thread.currentThread().getName() + ", end ,"+ new Date());
        }

        public synchronized void doLongTimeTaskC() {

            System.out.println("name = " + Thread.currentThread().getName() + ", begain ,"+ new Date());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("name = " + Thread.currentThread().getName() + ", end ,"+ new Date());

        }

    static class ThreadA extends Thread{

        private Task2 mTask2;

        public ThreadA(Task2 tk){
            mTask2 = tk;
        }

        @Override
        public void run() {
            mTask2.doLongTimeTaskA();
        }
    }

    static class ThreadB extends Thread{

        private Task2 mTask2;

        public ThreadB(Task2 tk){
            mTask2 = tk;
        }

        @Override
        public void run() {
            mTask2.doLongTimeTaskB();
        }
    }

    static class ThreadC extends Thread{

        private Task2 mTask2;

        public ThreadC(Task2 tk){
            mTask2 = tk;
        }

        @Override
        public void run() {
            mTask2.doLongTimeTaskC();
        }
    }

    public static void main(String[] args) {
        Task2 mTask2 = new Task2();
        ThreadA ta = new ThreadA(mTask2);
        ThreadB tb = new ThreadB(mTask2);
        ThreadC tc = new ThreadC(mTask2);

        ta.setName("A");
        tb.setName("B");
        tc.setName("C");

        ta.start();
        tb.start();
        tc.start();
    }
}
  • 执行的结果如下:

  • name = A, begain ,Sat Jun 11 09:45:49 CST 2022

  • name = C, begain ,Sat Jun 11 09:45:49 CST 2022

  • name = C, end ,Sat Jun 11 09:45:50 CST 2022

  • name = A, end ,Sat Jun 11 09:45:50 CST 2022

  • name = B, begain ,Sat Jun 11 09:45:50 CST 2022

  • name = B, end ,Sat Jun 11 09:45:51 CST 2022

  • 大家在跑的时候可以多跑几个例子,因为并不是每次跑该程序的结果都是一样的

  • 多跑几次结合数据我们可以发现由于doLongTimeTaskA方法和doLongTimeTaskB方法都是用static修饰的synchronize类锁,即同一个锁,所以 A和B是按顺序执行(类的层面上应该是整个进程唯一的),即同步的。

  • 而doLongTimeTaskC是对象锁,和A/B不是同一种锁,所以C和A、B是 异步执行的。(A、B、C代指上面的3中方法,A、B为类锁, C为对象锁即没有static修饰的方法)。

2、我们知道对象锁要想保持同步执行,那么锁住的必须是同一个对象。下面就修改下上面的代码来证明:

  • 在Task.java中修改ThreadA 和 ThreadB 中run代码,使ABC三个线程都调用doLongTimeTaskC()方法:
        @Override
        public void run() {
            mTask2.doLongTimeTaskC();
        }
  • 此时的main方法如下:
    public static void main(String[] args) {
        Task2 mTask2 = new Task2();
        Task2 mTask3 = new Task2();
        Task2 mTask4 = new Task2();
        ThreadA ta = new ThreadA(mTask2);
        ThreadB tb = new ThreadB(mTask3);
        ThreadC tc = new ThreadC(mTask4);

        ta.setName("A");
        tb.setName("B");
        tc.setName("C");

        ta.start();
        tb.start();
        tc.start();
    }
  • 输出结果如下:
  • name = C, begain ,Sat Jun 11 10:25:20 CST 2022
  • name = B, begain ,Sat Jun 11 10:25:20 CST 2022
  • name = A, begain ,Sat Jun 11 10:25:20 CST 2022

  • name = B, end ,Sat Jun 11 10:25:25 CST 2022
  • name = C, end ,Sat Jun 11 10:25:25 CST 2022
  • name = A, end ,Sat Jun 11 10:25:25 CST 2022
  • 从结果来看,前三行先打印,表示三个线程同时异步执行,各自线程sleep后再打印后面的语句。
  • 从代码中看,这三个线程拿到的对象并不是一个对象,各自线程有各自的对象,而方法C又是对象锁。
  • 这个锁生效的前提是三个线程拿到的是同一个 Task2 对象。才能保证代码的同步执行。
    public static void main(String[] args) {
        Task2 mTask2 = new Task2();
        ThreadA ta = new ThreadA(mTask2);
        ThreadB tb = new ThreadB(mTask2);
        ThreadC tc = new ThreadC(mTask2);

        ta.setName("A");
        tb.setName("B");
        tc.setName("C");

        ta.start();
        tb.start();
        tc.start();
    }
  • 可以看到线程A执行完自己run方法中调用的方法时,再去执行线程B,线程C 。
  • 第二种方法如果想要保证这三个线程中run方法中调用的方法同步执行的话,就将线程ABC三个线程中run方法中调用的doLongTimeTaskC改为doLongTimeTaskA或者doLongTimeTaskB,因为A,B是类锁,只要是这个类,就只能有一把锁。类锁对所有的该类对象都能起作用。
  • 不像对象,每个对象一把锁。
  • 两种方法实现三个线程同步。

3、总结

  • 1、如果多线程同时访问同一类的 类锁(synchronized 修饰的静态方法)以及对象锁(synchronized 修饰的非静态方法)这两个方法执行是异步的,原因:类锁和对象锁是两种不同的锁。
  • 2、类锁对该类的所有对象都能起作用,而对象锁不能;对象锁只能对该对象起作用。

版权声明:本文为qq_35604488原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。