首页 > 代码库 > java synchronized

java synchronized

  网上有许多关于synchronized关键字用法的文章,发现有些文章里介绍的用法和场景,这里我整理一下,通过实践,以一种有别于传统的写法介绍这个关键字的用法!用图文并茂的方式展示出来,希望大家理解起来更加简单易懂。本人知识有限,不足或错误的地方,欢迎指正,谢谢。

准备个实际测试用的例子

public class TestProgram extends Thread {
    
    static int j = 0;
    
    public static synchronized void testA(){
        j++;
        System.out.println(Thread.currentThread().getName() + ": " + j);
    }
    
    public synchronized void testB(){
        j++;
        System.out.println(Thread.currentThread().getName() + ": " + j);
    }
    
    public void testC(){
        System.out.println("into testC...");
        synchronized (this) {
            j++;
            System.out.println(Thread.currentThread().getName() + ": " + j);
        }
    }
   

    @Override
    public void run() {
        testA();
        testB();
        testC();
    }


这就是这个例子的所有代码,代码简单。这里简短介绍一下:

  总共有三个测试方法,分别是testA、testB和testC。testA方法是一个加了synchronized关键字和static关键字的方法,testB是只加了synchronized关键字的方法,testC是代码块中包含了synchronized块的方法,这里分别用两种模拟环境测试这三个方法的不同,用来区分三个方法在不同场景下的不同!

首先是测试环境(两个线程跑同一个program,我在这里只创建了一个TestProgram实例)

同一实例下测试

public static void main(String[] args) {
    TestProgram program = new TestProgram();
    Thread t = new Thread(program);
    Thread t2 = new Thread(program);
    t.start();
    t2.start();
}

测试testA()

这里我用两个线程去测试,通过start()调用run()方法,我在testA()方法里打了个断点,然后两个线程跑起来,我们看结果

我们在debug模式下,用多线程调试看结果,可以看到生成了Thread-1和Thread-2两个线程,在红色标记的代码中,可以看到Thread-1进入了testA()方法,而Thread-2则没有进入到testA()方法,这说明synchronized起到了同步作用。

接着我让线程Thread-1跑完testA()方法后,Thread-2方法就直接被激活了,直接进入testA()方法,同步有效了。

得到第一个结论,在同一个实例下,synchronized关键字是对static方法起到同步作用的。

当Thread-1和Thread-2都跑完testA()方法后输出结果:

Thread-1: 1
Thread-2: 2

为什么这个结论前面要加一个同一个实例?后面会看到,不在同一个实例下的多线程,synchronized关键效果不一样!

接下来是进入testB()方法

当Thread-1进入testB()方法时,看Thread-2线程进入了一种叫stepping的模式(类似于等待),在进入这个等待的过程中,如果我们将Thread-1线程跑完testB()方法,那么Thread-2的状态将由stepping改成suspended。简单的说就是当一个线程进入testB()时,其他线程都在等这个线程跑完。

在这里发现synchronized方法也起到了同步的效果。

再看测试testC()方法的效果

 

 我们可以看到,两个线程都进入到了testC()方法,而且两个线程的状态都是suspended。接下来,当线程Thread-1进入到synchronized代码块中,看结果

可以看到当线程Thread-1进入代码块后,Thread-2再进入这个代码块,状态就变成了stepping等待的情况了。说明这里synchronized方式同步有效。

最后执行testC()方法后的结果是:

into testC...
into testC...
Thread-1: 5
Thread-2: 6

 

以上就是在同一个实例下跑出来的结果。但如果是这种情况下,情况就不同了,这也是我们经常遇到的问题,有时候感觉怎么与以前的理论相驳,接着分析

不同实例下的测试

public static void main(String[] args) {
    TestProgram program = new TestProgram();
    TestProgram program2 = new TestProgram();
    Thread t = new Thread(program);
    Thread t2 = new Thread(program2);
    t.start();
    t2.start();
}

换了两行代码,在两个相同的TestProgram中,创建两个对象,与上面的写法差异在两个线程跑各自的实例,这个时候synchronized的作用效果就不太一样了

首先是testA()方法

测试的结果是

Thread-3: 1
Thread-2: 2

这个结果和开始测试的结果一样,说明在这里synchronized起到了同步效果!

测试testB()

这里结果就和上面测试testB()效果就不一样了,我们看效果

从图中可以看到,两个线程都同时进入了testB()方法中,也就是synchronized关键字没起到同步两个线程的作用

这里得出一个结论:两个不同的实例,在不同的线程环境下,synchronized并不能实现同步效果,当然,static方法除外

后面的testC()方法也一样,看看效果

两个线程都同时进入了第22行代码,就是同时在synchronized块中,说明这个synchronized代码块没有起到同步的效果。

 

综合以上所诉,我得出一个这样的结论:

如果多线程是对同一个对象的引用,那么synchronized关键字在所有的情况下都有同步的效果,如果多线程是对同一个对象的不同引用(创建了多个对象),除了static方法上面加的synchronized方法有同步引用的效果外,其他的synchronized(包括synchronized代码块)将没有同步的效果。

 

至于为什么static方法上加synchronized方法,在不同的场景下都有效,原因是:被static修饰的成员变量和成员方法独立于该类的任何对象,static方法不属于类创建的引用,所以无论创建了多少个对象,对于static方法而言,都跟它没关系。

ps:synchronized(this)这个this就是指对象的引用,这里指我当前线程的对象引用。

我的疑问:为什么创建一个对象调试的时候,是Thread-1和Thread-2,而我创建两个对象调试的时候却是Thread-2和Thread-3?