synchronized使用static修饰与不使用static修饰的区别

Spring Wu 380 2021-02-03

synchrinized

该关键字是一个同步锁,就是当一个线程进入synchronized修饰的代码后,执行完毕之后,释放锁。其他线程才可竞争此锁。该锁可以用于锁代码块、锁实例方法、锁静态方法,但是其中是存在很大的区别的。

question1
  • 使用synchronized修饰同一个类中的两个方法,使用同一个对象调用两个方法,最后的输出结果是什么?
public class SynchronizedDemo {

    public static void main(String[] args) {
        final TestPrint print = new TestPrint();
        new Thread(() -> print.printOne(), "Thread1").start();
        new Thread(() -> print.printTwo(), "Thread2").start();
    }

}

class TestPrint {

    /**
     * 让线程1进来之前先sleep 1秒钟
     */
    public synchronized void printOne() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        System.out.println("One");
    }

    
    public synchronized void printTwo() {
        System.out.println("Two");
    }
}

运行后的结果为:One Two。 代码中Thread1运行printOne()方法时,休眠了1秒,Thread2没有马上执行逻辑,而是等到Thread1运行完毕后,Thread2才开始运行。
说明Thread2线程确实按照预想的等待Thread1锁释放后,才去拿了锁执行。

question2
  • 如果我使用两个线程运行两个对象实例,运行结果还是:One、Two吗?
public class SynchronizedDemo {

    public static void main(String[] args) {
        final TestPrint print1 = new TestPrint();
        final TestPrint print2 = new TestPrint();
        new Thread(() -> print1.printOne(), "Thread1").start();
        new Thread(() -> print2.printTwo(), "Thread2").start();

    }
}

class TestPrint {

    /**
     * 让线程1进来之前先sleep 1秒钟
     */
    public synchronized void printOne() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        System.out.println("One");
    }


    public synchronized void printTwo() {
        System.out.println("Two");
    }
}

结果是:Two、One。意味着Thread2线程并没有等待Thread1执行完后再执行。而是Thread1在sleep的时候,Thread2直接执行了。原因是,print1和print2属于不同的对象。而实例方法的锁是属于对象的。所以才会不生效。除非是同一个对象调用这个两个方法。

question3
  • 如果一个是static修饰的静态方法,另一个是实例方法。使用同一个对象调用printOne()printTwo(),最后的运行结果是?
public class SynchronizedDemo {

    public static void main(String[] args) {
        final TestPrint print1 = new TestPrint();
        new Thread(() -> print1.printOne(), "Thread1").start();
        new Thread(() -> print1.printTwo(), "Thread2").start();

    }
}

class TestPrint {

    /**
     * 让线程1进来之前先sleep 1秒钟
     */
    public static synchronized void printOne() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        System.out.println("One");
    }


    public synchronized void printTwo() {
        System.out.println("Two");
    }
}

结果为:Two、One,通过结果来看,同一个print1对象调用printOne()printTwo()printTwo并没有等待printOne()执行完毕再执行。原因是static修饰的锁是类锁,锁住的是整个Class,非static修饰的,锁住的是Class的实例信息。所以每当你创建一个新对象,虽然访问的是同一个方法,但是锁不会管用。这里虽然使用的是同一个对象,但是锁存在于不同的地方,一个锁在对象中,一个锁在Class中。所以,其实锁并没有失效。只是用的方式不对。

question4
  • 使用static修饰两个synchronized方法。最后通过同一个对象用两个线程调用这两个方法,最后输出结果是什么?
public class SynchronizedDemo {

    public static void main(String[] args) {
        final TestPrint print1 = new TestPrint();
        new Thread(() -> print1.printOne(), "Thread1").start();
        new Thread(() -> print1.printTwo(), "Thread2").start();

    }
}

class TestPrint {

    /**
     * 让线程1进来之前先sleep 1秒钟
     */
    public static synchronized void printOne() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        System.out.println("One");
    }


    public static synchronized void printTwo() {
        System.out.println("Two");
    }
}

结果为:One、Two。原因也很简单,因为两个方法的锁都在Class中,所以调用不同的方法,作用还是在整个Class中。

question5
  • 使用static修饰两个synchronized方法,然后通过不同的对象进行调用,最后的输出结果是什么?
public class SynchronizedDemo {

    public static void main(String[] args) {
        final TestPrint print1 = new TestPrint();
        final TestPrint print2 = new TestPrint();
        new Thread(() -> print1.printOne(), "Thread1").start();
        new Thread(() -> print2.printTwo(), "Thread2").start();

    }
}

class TestPrint {

    /**
     * 让线程1进来之前先sleep 1秒钟
     */
    public static synchronized void printOne() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        System.out.println("One");
    }


    public static synchronized void printTwo() {
        System.out.println("Two");
    }
}

结果为:One、Two,static修饰的方法可以不通过对象来调用,因为static修饰的变量、代码块、方法在类加载期间就已经分配内存了,而非static修饰的变量、代码块、方法需要在创建对象的时候分配内存,所以我们不需要创建对象也可以调用静态方法。而这里使用两个对象调用,最后结果是One、Two的原因就是,虽然表面上看起来是两个对象,但是在内存指针中其实print1对象和print2对象是指向同一块内存的。而这块内存中存放的就是Class的信息,当然也包括了锁信息。