带你揭开 synchronized 同步机制的神秘面纱

640 阅读5分钟
原文链接: blog.csdn.net
    相信大家在进行Java开发的时候经常会接触到同步的概念,在多线程并发的情况下,为保证同一个时间点只能被一个线程访问到,就需要用到同步机制。

    对于一段代码片,或者一个方法怎么进行线程同步?这时就会用到我们今天的主角(synchonized)了。我们日常使用synchonized的时候,经常会直接在方法前面加上synchonized关键字,或者用synchonized(this)直接修饰一段代码片等,就能实现同步操作了。但是不同的使用情况有什么区别?作用范围又是什么?相信还是有很多同学不甚了解吧。其实它涉及到类锁和对象锁两个概念呢!!

    今天我们就一起来探讨synchonized的多种用法,通过代码来分析各种情况的意义和原理。
    首先我们模拟多线程同时访问一个类的被synchonized修饰方法和未被synchonized修饰的方法:

(1)在person内部有synchonized修饰方法和普通方法:

public class Person {

    private String name;

    public Person(String name) {
        this.name = name;
    }

    public synchronized void sayHello(){
        for(int i=0;i<3;i++){
            try {
                System.out.println(name+" say hello: "+i);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void sayWelcome(){
        for(int i=0;i<3;i++){
            try {
                System.out.println(name+" say Welcome: "+i);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

调用测试方法,不同线程同时访问synchonized修饰的sayhello()方法和普通方法saywelcome():

        final Person person1 = new Person("person1");
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                person1.sayHello();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                person1.sayWelcome();
            }
        });
        thread1.start();
        thread2.start();

控制台输出结果为:

 person1 say hello: 0
 person1 say Welcome: 0
 person1 say hello: 1
 person1 say Welcome: 1
 person1 say hello: 2
 person1 say Welcome: 2

结果显示:一个线程访问对象的synchonized修饰同步方法时,另一个线程仍然可以访问对象的非synchonized修饰的方法。

(2)模拟不同线程同时访问同一对象的同步方法:

        final Person person1 = new Person("person1");
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                person1.sayHello();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
            //也调用sayHello同步方法
              person1.sayHello();
            }
        });
        thread1.start();
        thread2.start();

直接修改测试代码,两个线程同时调用synchonized修饰的sahello()方法。
控制台输出结果为:

person1 say hello: 0
person1 say hello: 1
person1 say hello: 2
person1 say hello: 0
person1 say hello: 1
person1 say hello: 2

结果显示:一个线程访问对象的synchonized修饰同步方法时,另一个线程想要访问对象的synchonized修饰的方法时需要等待。

(3)模拟不同线程同时访问不同对象的同步方法:
仍然不需要修改person中的代码,测试代码修改为:

        final Person person1 = new Person("person1");
        final Person person2 = new Person("person2");
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                person1.sayHello();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                person2.sayHello();
            }
        });
        thread1.start();
        thread2.start();

控制台输出为:

 person1 say hello: 0
 person2 say hello: 0
 person1 say hello: 1
 person2 say hello: 1
 person1 say hello: 2
 person2 say hello: 2

结果显示:不同线程访问同一个类的不同对象的同一同步方法时,没有任何影响。这就是上面提到的对象锁,只是在对象内有效。

修改person类中同步代码的书写方式为:

 public void sayHello(){
        synchronized (this) {
            for (int i = 0; i < 3; i++) {
                try {
                    System.out.println(name + " say hello: " + i);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

(4) 在不同线程同时访问同步方法和非同步方法时,控制台的输出为:

 person1 say hello: 0
 person1 say Welcome: 0
 person1 say hello: 1
 person1 say Welcome: 1
 person1 say hello: 2
 person1 say Welcome: 2

(5)在不同线程同时访问同一对象的同步方法时,控制台的输出为:

person1 say hello: 0
person1 say hello: 1
person1 say hello: 2
person1 say hello: 0
person1 say hello: 1
person1 say hello: 2

(6)在不同线程分别访问同一类不同对象的同步方法时,控制台的输出为:

 person1 say hello: 0
 person2 say hello: 0
 person1 say hello: 1
 person2 say hello: 1
 person1 say hello: 2
 person2 say hello: 2

通过例4、5、6的测试,可以看出分别和例1、2、3的结果完全相同。

结论:通过在方法前加synchonized关键字和在方法内使用synchonized(this)进行方法同步时,起到相同的效果,它们都属于对象锁,是对对象进行加锁。访问同一个类的不同实例间的同步方法互不影响

修改person类sayhello()方法为:

  public void sayHello(){
        synchronized (Person.class) {
            for (int i = 0; i < 3; i++) {
                try {
                    System.out.println(name + " say hello: " + i);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

(7)在不同线程同时访问同步方法和非同步方法时,控制台的输出为:

 person1 say hello: 0
 person1 say Welcome: 0
 person1 say hello: 1
 person1 say Welcome: 1
 person1 say hello: 2
 person1 say Welcome: 2

(8)在不同线程同时访问同一对象的同步方法时,控制台的输出为:

person1 say hello: 0
person1 say hello: 1
person1 say hello: 2
person1 say hello: 0
person1 say hello: 1
person1 say hello: 2

(9)在不同线程分别访问同一类不同对象的同步方法时,问题就出现在这里,测试代码为:

        final Person person1 = new Person("person1");
        final Person person2 = new Person("person2");
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                person1.sayHello();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                person2.sayHello();
            }
        });
        thread1.start();
        thread2.start();

控制台的输出结果为:

person1 say hello: 0
person1 say hello: 1
person1 say hello: 2
person1 say hello: 0
person1 say hello: 1
person1 say hello: 2

和测试用例(3和6)出现了差异,为什么不同的线程,访问不同对象的方法都会出现同步的情况??

原因是使用synchronized(class)的方式进行同步的时候,是加了一个类锁。作用范围扩大为类,不再局限为对象了。无论创建多少个对象,都是这个类的实例,所以会存在同步的效果。