在网上看了许多关于synchronized的介绍及用法区别,大多大同小异,点到为止,个人推荐一篇博友写的,
网址如下:http://blog.csdn.net/cs408/article/details/48930803
这篇博客是介绍对象锁和类锁的区别,通俗易懂,而我的这篇博客是基于此基础上引申出,用实际的代码分析对象和类在方法、代码段加锁及静态时的对比。
开始先普及一下java里synchronized锁的一些概念:有分为对象锁和类锁两种。对象锁(又称作实例锁),是指锁在某一个实例对象上的;类锁(又称作全局锁),是锁针对类上的,无论实例多少该类对象,都是共享该锁。
一、首先是这样一个列子,设计一个多线程的类TestSynchronized,有如下方法:
class TestSynchronized{
public synchronized void test_one(){ //加锁的实例方法
System.out.println(Thread.currentThread().getName()+" begin time: "+System.currentTimeMillis());
//记录某线程进入此方法时的时间
try{
Thread.sleep(1000); //某线程睡眠1秒
}catch (Exception e) {}
System.out.println(Thread.currentThread().getName()+" end time: "+System.currentTimeMillis());
//记录某线程结束此方法时的时间
}
public synchronized void test_one_cope(){ //加锁的实例方法
//内容同上方法内....
}
public static synchronized void test_two(){ //加锁的类方法
//内容同上方法内....
}
public static synchronized void test_two_cope(){ //加锁的类方法
//内容同上方法内....
}
}
在主函数测试中,给每个方法对应一个线程:
Thread thread = new Thread( //一个线程测一个方法
new Runnable() {
public void run() {
//test(); //方法名
}
}
);
thread.setName("..."); //给线程添加名字,便于结果读取
新建两个TestSynchronized对象A和B,有如下对比:
测试序号 | 对象 | 静态 | 加锁方法 | 对象 | 静态 | 加锁方法 |
1 | A | × | test_one() | A | × | test_one_cope() |
2 | A | × | test_one() | B | × | test_one() |
3 | A | × | test_one() | A | √ | test_two() |
4 | A | √ | test_two() | B | √ | test_two() |
5 | A | √ | test_two() | B | √ | test_two_cope() |
1.同一个对象访问两个加锁的方法:相互约束
a begin time: 1517142794345
a end time: 1517142795345
cope_a begin time: 1517142795345
cope_a end time: 1517142796345
2.两个对象访问同一个加锁的方法:同时访问
a begin time: 1517143258663
b begin time: 1517143258663
b end time: 1517143259663
a end time: 1517143259663
3.同一个对象访问加锁的方法与加锁的静态方法:同时访问
c begin time: 1517147555199
a begin time: 1517147555199
a end time: 1517147556199
c end time: 1517147556199
4.两个对象访问同一个加锁的静态方法:相互约束
c begin time: 1517147900217
c end time: 1517147901217
d begin time: 1517147901217
d end time: 1517147902217
5.两个对象访问不同的两个加锁静态方法:相互约束
c begin time: 1517148173378
c end time: 1517148174378
cope_d begin time: 1517148174378
cope_d end time: 1517148175378
以上四个例子是大多数博客中列举到的synchronized与static synchronized的对比区别,其结论无非是一下几点:
1.当加锁的方法是实例方法时,同一个对象在多线程情况下不能同时访问加锁的实例方法,换句话意思是,同一个实例内的加锁实例方法是相互约束的。当然,对于不同的实例对象,因为在不同的域,就没有这种约束。
2.当加锁的方法是类方法时,属于该类的对象在多线程情况下不能同时访问加锁的静态方法,也就是加锁的静态方法在该类下都是相互约束的,作用域是整个类。
3.线程可同时获得实例方法和类方法的锁,这点很关键。看测试3结果就能发现,类锁和对象锁控制着两个不同区域,互不约束。
二、那么,当synchronized是锁在方法的代码块中,以synchronized(this){}方式,而非锁方法,结果会如何呢?
先在类TestSynchronized中添加一些锁代码段的代码:
class TestSynchronized{
//...
public void test_three(){ //实例方法加锁代码块
System.out.println(Thread.currentThread().getName()+" begin time: "+System.currentTimeMillis());
//记录某线程进入此方法时的时间
synchronized (this) {
System.out.println(Thread.currentThread().getName()+" now time: "+System.currentTimeMillis());
//记录某线程进入此代码块时的时间
try{
Thread.sleep(1000); //某线程睡眠1秒
}catch (Exception e) {}
System.out.println(Thread.currentThread().getName()+" end time: "+System.currentTimeMillis());
//记录某线程出此代码块时的时间
}
}
public void test_three_cope(){ //实例方法加锁代码块
//内容同上方法内....
}
}
如下对比:
测试序列 | 对象 | 方法 | 锁代码 | 对象 | 方法 | 锁代码 |
6 | A | test_three() | √ | A | test_three_cope() | √ |
7 | A | test_three() | √ | B | test_three() | √ |
8 | A | test_three() | √ | A | test_one() //加锁方法 | × |
对比测试及结果是:
6.同一个对象访问两个加锁的代码块(synchronized(this)):相互约束
e begin time: 1517148948884
cope_e begin time: 1517148948884
e now time: 1517148948884
e end time: 1517148949884
cope_e now time: 1517148949884
cope_e end time: 1517148950884
7.两个对象访问同一个加锁的代码块(synchronized(this)):同时访问
f begin time: 1517149480694
e begin time: 1517149480694
f now time: 1517149480694
e now time: 1517149480694
f end time: 1517149481694
e end time: 1517149481694
8.同一个对象访问加锁的方法,一个访问加锁的代码块(synchronized(this)):相互约束
a begin time: 1517149816536
e begin time: 1517149816536
a end time: 1517149817536
e now time: 1517149817536
e end time: 1517149818536
通过对加锁代码块(synchronized(this))与 synchronized method比对,得出如下结论:
synchronized methods(){} 与synchronized(this){}之间没有太大区别,synchronized(this){}就是在方法内同步代码块,相当于缩小了冲突的区域,表现更高效率。所以当加锁的代码块时,同一个实例对象在多线程情况下不能同时访问加锁的代码块,但是方法的未加锁段是可以同时访问的。从测试8可以看出,加锁的方法与加锁的代码块(synchronized(this){})对于同一个实例,是在相同的域内,无法同时访问的。
所以,同一个对象访问加锁的静态方法和访问加锁的代码块也没有可比性,因为两个作用域不同。
可能有人会联想到,那静态的方法内加同步代码块呢?其实小编已经测验过,结论与全局锁相似,当访问到加锁的代码段时,属于该类的对象多线程访问都会相互约束。
三、接下来分析锁代码快中,以synchronized(class){}方式。
class TestSynchronized{
//...
public void test_four(){ //实例方法加锁代码块
//...
synchronized (TestSynchronized.class) {
//...
}
}
public void test_four_cope(){ //实例方法加锁代码块
//内容同上方法内....
}
}
测试序列 | 对象 | 方法 | 锁对象 | 对象 | 方法 | 锁对象 | 结果 |
9 | A | test_four() | class | A | test_four_cope() | class | 相互约束 |
10 | A | test_four() | class | B | test_four() | class | 相互约束 |
11 | A | test_one() //加锁的方法 | × | A | test_four() | class | 同时访问 |
12 | A | test_two() //加锁的静态方法 | × | A | test_four() | class | 相互约束 |
13 | A | test_three() | this | A | test_four() | class | 同时访问 |
14 | A | test_one() //加锁的方法 | × | B | test_four() | class | 同时访问 |
15 | A | test_two() //加锁的静态方法 | × | B | test_four() | class | 互相约束 |
16 | A | test_three() | this | B | test_four() | class | 同时访问 |
通过测试序列9~12,其实就能得出结论:
以synchronized(class){}的方式锁代码块时,实例对象间在多线程情况下无法同时访问该段代码块,进入该代码的线程获得了整个类的全局锁,相当于被锁住的这段代码变成了静态的代码块。
对于测试11和13,可以知道,与加锁的静态方法一样,同个实例对象访问以synchronized(class){}的方式锁代码块时的线程,与访问对象锁时的线程是在两个域内的,互不影响。
测试14~16,是不同实例进行比较,验证结论如上。
以上测试对比,个人认为还算详细,当然还有一些可以比较的,小编没有一一列出。
通过以上说明,小编做了一个图表供参考:
Synchronized |
|||
对象锁(实例锁) |
类锁(全局锁) |
||
synchronized method(){} |
synchronized(this){} |
static synchronized method(){} |
synchronized(class){} |
在同个实例对象内,所属线程独占对象锁,其他线程阻塞;不同的实例对象内无影响。 |
在同个类内,所属线程独占类锁,其他线程阻塞。 |
在使用synchronized关键字时候,应该尽可能避免在synchronized方法或synchronized块中使用sleep或者yield方法,因为synchronized程序块占有着锁,该线程休息但不会释放资源,严重影响效率。小编的测试代码内使用sleep只是为了测试易于观察。
本篇博客只是对关键字synchronized做了表面且不完善的比较,比如引申出:
1. 锁代码块锁的是对象变量,类变量甚至是方法传入的参数变量,会有什么不同?
2. 锁之间的重入问题是怎样?
3. 锁方法之间的访问等等。
所以小编接下来还会继续深入得探究分析synchronized的实现原理及在并发中的比较。