前言
对于synchronized关键字,想必是个小白都应该听说过,在日常的面试中也会经常问到对于这个关键字的理解,但是恐怕没有多少人敢说自己对synchronized很了解,当然我也是。正是因为不熟悉,所以这里和大家一起来掀开它的面纱,一睹synchronized的真容。
同步方法和同步代码块的区别
synchronized无非就是加锁,那这个锁是加在哪里呢,在类上面,方法上,还是方法中的核心逻辑代码上面?加锁的目的是为了避免多个线程对于统一资源的更改,导致资源不一致。我们看看synchronized是如何加在方法上和代码上的区别
/**
* @ClassName: CountDemo
* @Description: TODO(同步方法)
* @author 爱琴孩
*/
public class CountDemo {
public synchronized void synchMethodTest(){
for(int i=0;i<5;i++){
System.out.println("当前线程是"+Thread.currentThread().getName()+",i="+i);
}
}
}
同步代码块
/**
* @ClassName: CountDemo
* @Description: TODO(同步代码块)
* @author 爱琴孩
*/
public class CountDemo {
public void synchCodeBlockTest(){
synchronized(this){
for(int i=0;i<5;i++){
System.out.println("当前线程是"+Thread.currentThread().getName()+",i="+i);
}
}
}
}
测试客户端如下
/**
* @ClassName: CountClient
* @Description: TODO(测试客户端)
* @author 爱琴孩
*/
public class CountClient extends Thread{
private CountDemo countDemo;
public CountClient(){};
public CountClient(CountDemo countDemo){
this.countDemo=countDemo;
}
public void run(){
countDemo.synchMethodTest();
//countDemo.synchCodeBlockTest();
}
public static void main(String[] args) {
CountDemo countDemo=new CountDemo();
CountClient thread1=new CountClient(countDemo);
CountClient thread2=new CountClient(countDemo);
thread1.start();
thread2.start();
}
}
上面的代码块和同步方法运行的结果其实是一样的,但是还有区别的,同步方法对于那些非核心逻辑代码也实行了同步,这样相对与同步代码块需要耗费更多的时间来用在同步上,也就意味着其他线程需要更长时间来等待当前线程释放对象锁,对于程序而言,多余时间的消耗是无法忍受的,所以谁更节约时间就不用讲了。
对象锁而不是代码锁
java中每一个对象都有一个锁,当对象调用加了synchronized关键字的方法或者代码块的时候,该方法或者说该代码块就会获得对象的锁,当其他线程来调用同一个对象的同步方法的时候,需要等当前线程释放这个对象的锁,其他线程才能去调用这个对象的同步方法。这里需要注意的是,同一个对象。如果不是同一个对象那么同步有没有效果呢?
依旧调用上面的synchMethodTest,在测试客户端中修改一下
/**
* @ClassName: CountClient
* @Description: TODO(测试不同对象的同步方法是否会堵塞)
* @author 爱琴孩
*/
public class CountClient extends Thread{
private CountDemo countDemo;
public CountClient(){};
public CountClient(CountDemo countDemo){
this.countDemo=countDemo;
}
public void run(){
countDemo.synchMethodTest();
}
public static void main(String[] args) {
CountClient thread1=new CountClient(new CountDemo ());
CountClient thread2=new CountClient(new CountDemo ());
thread1.start();
thread2.start();
}
}
测试结果如下
可以看到,上面两个线程是交替执行的,也就是说并没堵塞,这也就佐证了,锁是锁住了对象,而不是锁住代码块。
全局锁和局部锁
synchronized加在非静态方法上,那么这是获得调用该方法的对象所属的锁。这种锁被称为局部锁,如果是加在静态方法上,那么这个锁是属于类锁,也就是所谓的全局锁。
/**
* @ClassName: CountDemo
* @Description: TODO(同步静态方法)
* @author 爱琴孩
*/
public class CountDemo {
public static synchronized void synchStaticMethodTest(){
for(int i=0;i<5;i++){
System.out.println("当前线程是"+Thread.currentThread().getName()+",i="+i);
}
}
}
上面的是采用同步静态方法,当然我们还可以采用同步静态代码块,这里需要区别的就是synchronized(this){}和synchronized(Object.class){}这两种写法,对于synchronized(this){}是局部同步代码块,而synchronized(Object.class){}是全局同步代码块。
同样用两个不同的对象来测试,静态同步方法
/**
* @ClassName: CountClient
* @Description: TODO(测试不同对象的静态同步方法是否会堵塞)
* @author 爱琴孩
*/
public class CountClient extends Thread{
private CountDemo countDemo;
public CountClient(){};
public CountClient(CountDemo countDemo){
this.countDemo=countDemo;
}
public void run(){
countDemo.synchStaticMethodTest();
}
public static void main(String[] args) {
CountClient thread1=new CountClient(new CountDemo ());
CountClient thread2=new CountClient(new CountDemo ());
thread1.start();
thread2.start();
}
}
测试结果如下
可以看到,这里两个对象还是堵塞了,因为静态同步方法是锁住了所有的对象。
happens-before原则
在获得一个对象锁之前,要先打开这个对象加在别的地方的锁,听起来很模糊,估计是我的表达有问题,可以结合下面的例子来看
接地气一点的说法是,一个线程调用一个对象的同步方法,然后另一个线程调用同一个对象另一同步方法,需要等待上面那个现成释放该对象的锁之后,才能调用另一个同步方法
这里一个类中有两个同步方法
/**
* @ClassName: CountDemo
* @Description: TODO(同步方法)
* @author 爱琴孩
*/
public class CountDemo {
public synchronized void synchMethodTest(){
for(int i=0;i<5;i++){
System.out.println("当前线程是"+Thread.currentThread().getName()+",i="+i);
}
}
public synchronized void doOtherThing(){
System.out.println("开始测试,开始之后需要休眠!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("休眠之后重新启动!");
}
}
测试客户端如下
/**
* @ClassName: CountClient
* @Description: TODO(测试客户端)
* @author 爱琴孩
*/
public class CountClient extends Thread{
private CountDemo countDemo;
public CountClient(){};
public CountClient(CountDemo countDemo){
this.countDemo=countDemo;
}
public static void main(String[] args) {
final CountDemo countDemo=new CountDemo();
new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
countDemo.doOtherThing();
}
}).start();
new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
countDemo.synchMethodTest();
}
}).start();
}
}
测试结果如下
上述结果也佐证了上面的说法是正确。
这里对于synchronized关键字简单介绍,如有不足之处,还请不吝指正!