对synchronized的理解和Spring为什么是单例的(Understanding of synchronized and why spring is a singleton)

只有真正理解了Java中对象是什么,才能理解这个关键字是什么意思

字面解释

Java Guide中如此解释:

synchronized 关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

synchronized 关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

测试

但是这句话很多时候是有误导性的,synchronized这个关键字并不能保证同一时间只有一个线程访问,确切说,如果是用同一个对象调用方法的时候,方法的确是同一时间只能有一个线程访问:

class Solution {

    public static void main(String[] args) throws Exception {
        Node node1 = new Node();
        Node node2 = new Node();
        new Thread(node1::test).start();
        new Thread(node1::test).start();

        System.out.println("主线程结束");

    }
}

class Node {

    public synchronized void test() {
        System.out.println("!!!!!!");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("完成!!!");
    }
}

输出为

!!!!!!
主线程结束
完成!!!
!!!!!!
完成!!!

!!!!!!
主线程结束
完成!!!
!!!!!!
完成!!!

没有问题,一个时间走一个线程。

但是,如果你用两个不同的对象调用同一个方法,synchronized关键字是无用的:

class Solution {

    public static void main(String[] args) throws Exception {
        Node node1 = new Node();
        Node node2 = new Node();
        new Thread(node1::test).start();
        new Thread(node2::test).start();

        System.out.println("主线程结束");

    }
}

class Node {

    public synchronized void test() {
        System.out.println("!!!!!!");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("完成!!!");
    }
}

输出为:

!!!!!!
!!!!!!
主线程结束
完成!!!
完成!!!

!!!!!!
!!!!!!
主线程结束
完成!!!
完成!!!

这时候为什么同步监视器没用了呢?原因在于,synchronized加在普通方法的时候,当一个线程访问这个方法的时候,持有的是当前类实例对象的同步监视器,也就是说当node1调用test()的时候,node1本身被他自己的线程new Thread持有了。这时候如果node2再次调用test(),由于node2自己没有被任何线程持有,所以synchronized此时是失效的。如果是用node1对象调用调用了test(),这时node1被线程1持有以后,第二个new出来的线程是无法持有node1的,就只能等待。

所以一切都基于对Java“对象”这个概念的理解。

当synchronized加在静态方法上的时候,线程持有的是类的Class对象:

class Solution {

    public static void main(String[] args) throws Exception {
        Node node1 = new Node();
        Node node2 = new Node();
//        new Thread(node1::test).start();
//        new Thread(node2::test).start();
        new Thread(() -> {
            node1.test();
        }).start();

        new Thread(() -> {
            node2.test();
        }).start();

        System.out.println("主线程结束");

    }
}

class Node {

    public static synchronized void test() {
        System.out.println("!!!!!!");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("完成!!!");
    }
}

这种情况下,即便两次调用test()方法属不同的对象,但是,由于线程持有的clazz对象是单例的,所以依然达到了同步的效果:

!!!!!!
主线程结束
完成!!!
!!!!!!
完成!!!

!!!!!!
主线程结束
完成!!!
!!!!!!
完成!!!

对于同步代码块,也是一样的操作,一般情况下我们会将同步代码块中传入this,就类似于将方法调用者的对象作为了同步监视器,这样的操作在单例模式的基础上是可以达到同步效果的。

Spring为什么是单例的

所以从这里可以看出,Spring为什么会把组件都设置为单例的呢?一方面Spring中各个组件的功能实现不需要多实例,请求和请求之间方法调用多为无状态的,当多个查询数据库的请求调用到DAO层的时候,自然有mybatis帮我们实现代理类的不同对象去隔离不同请求的数据,在Controller和Service构建多实例对象浪费内存空间;另一方面,单例是有助于实现同步效果的。当我们在控制器接口的方法声明为synchronized,这时用这个controller调用这个方法的时候,默认是用controller对象自己作为同步监视器的,而controller对象自然是满足单例的,这样就自然满足了同步的要求。

————————

Only when you really understand what the object is in Java can you understand the meaning of this keyword

Literal interpretation

The Java guide explains this:

The synchronized keyword solves the synchronization of accessing resources between multiple threads. The synchronized keyword can ensure that only one thread can execute the modified method or code block at any time.

The synchronized keyword solves the synchronization of accessing resources between multiple threads. The synchronized keyword can ensure that only one thread can execute the modified method or code block at any time.

test

However, this sentence is often misleading. The keyword synchronized does not guarantee that only one thread can access the method at the same time. Specifically, if the method is called with the same object, the method can only be accessed by one thread at the same time:

class Solution {

    public static void main(String[] args) throws Exception {
        Node node1 = new Node();
        Node node2 = new Node();
        new Thread(node1::test).start();
        new Thread(node1::test).start();

        System.out.println("主线程结束");

    }
}

class Node {

    public synchronized void test() {
        System.out.println("!!!!!!");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("完成!!!");
    }
}

Output as

!!!!!!
End of main thread
Done!!!
!!!!!!
Done!!!

!!!!!!
End of main thread
Done!!!
!!!!!!
Done!!!

No problem, one thread at a time.

However, if you call the same method with two different objects, the synchronized keyword is useless:

class Solution {

    public static void main(String[] args) throws Exception {
        Node node1 = new Node();
        Node node2 = new Node();
        new Thread(node1::test).start();
        new Thread(node2::test).start();

        System.out.println("主线程结束");

    }
}

class Node {

    public synchronized void test() {
        System.out.println("!!!!!!");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("完成!!!");
    }
}

Output is:

!!!!!!
!!!!!!
End of main thread
Done!!!
Done!!!

!!!!!!
!!!!!!
End of main thread
Done!!!
Done!!!

Why is the synchronization monitor useless at this time? The reason is that when synchronized is added to an ordinary method, when a thread accesses this method, it holds the synchronization monitor of the current class instance object, that is, when node1 calls test (), node1 itself is held by its own thread new thread. At this time, if node2 calls test () again, because node2 itself is not held by any thread, synchronized is invalid at this time. If test () is called with node1 object, then after node1 is held by thread 1, the second new thread cannot hold node1 and can only wait.

So everything is based on the understanding of the concept of Java “object”.

When synchronized is added to the static method, the thread holds the class object of the class:

class Solution {

    public static void main(String[] args) throws Exception {
        Node node1 = new Node();
        Node node2 = new Node();
//        new Thread(node1::test).start();
//        new Thread(node2::test).start();
        new Thread(() -> {
            node1.test();
        }).start();

        new Thread(() -> {
            node2.test();
        }).start();

        System.out.println("主线程结束");

    }
}

class Node {

    public static synchronized void test() {
        System.out.println("!!!!!!");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("完成!!!");
    }
}

In this case, even if the two calls to the test () method belong to different objects, the clazz object held by the thread is a single instance, so the synchronization effect is still achieved:

!!!!!!
End of main thread
Done!!!
!!!!!!
Done!!!

!!!!!!
End of main thread
Done!!!
!!!!!!
Done!!!

The same operation applies to the synchronization code block. Generally, we will pass this into the synchronization code block, which is similar to taking the object of the method caller as the synchronization monitor. Such operation can achieve the synchronization effect on the basis of singleton mode.

Why is spring singleton

Therefore, it can be seen from here that why does spring set all components to singleton? On the one hand, the function implementation of each component in spring does not require multiple instances, and the method calls between requests are mostly stateless. When multiple requests to query the database are called to the Dao layer, mybatis naturally helps us realize different objects of the proxy class to isolate the data of different requests, and building multi instance objects in the controller and service wastes memory space; On the other hand, singleton is helpful to achieve synchronization effect. When we declare the method of the controller interface as synchronized, when we call this method with this controller, the controller object itself is used as the synchronization monitor by default, and the controller object naturally meets the singleton, which naturally meets the requirements of synchronization.