网上有许多关于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?