对synchronized的理解和Spring为什么是单例的

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

字面解释

Java Guide中如此解释:

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对象自然是满足单例的,这样就自然满足了同步的要求。