synchronized对于加锁代码块、方法以及全局(static)锁的详细对比

在网上看了许多关于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(){ //实例方法加锁代码块
		//内容同上方法内....
	}
}

考虑到锁代码块括号内参数是this时是变成了对象锁,那么synchronized(class){}会不会是类锁呢?所以做出了以下对比及结果:
测试序列 对象 方法 锁对象 对象 方法 锁对象 结果
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的实现原理及在并发中的比较。

你可能感兴趣的:(Java,并发,锁)