关于synchronized的一些小实验

一、准备工作:

1、新建获取下一流水号的工具类

public class IdTool {

	private static int currentIdValue = 0;
	
	public static int getNextId(){
		
		currentIdValue = currentIdValue+1;
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return currentIdValue;
	}
}

2、新建测试类

public class Test extends Thread {

	@Override
	public void run() {
		for(int i=0;i<3;i++){		
			System.out.println(Thread.currentThread().getName()
					+",getNextId()="+IdTool.getNextId());
		}
	}	
	public static void main(String[] args) {

		Test t1 = new Test();
		Test t2 = new Test();
		
		t1.start();
		t2.start();
	}
}

二、分场景测试

1、不加synchronized关键字的场景,即上面的代码,不需做任何修改,执行结果:

Thread-0,getNextId()=2
Thread-1,getNextId()=2
Thread-0,getNextId()=4
Thread-1,getNextId()=4
Thread-1,getNextId()=6
Thread-0,getNextId()=6

小结: 多线程场景下,对于公共资源的访问、修改,如果不做线程同步处理,很有可能出现数据混乱。

 

2、static方法添加synchronized的场景,把getNextId()方法加上synchronized修饰符,修改后的代码如下:

扫描二维码关注公众号,回复: 1379825 查看本文章
public class IdTool {

	private static int currentIdValue = 0;
	
	public static synchronized int getNextId(){
		
		currentIdValue = currentIdValue+1;
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return currentIdValue;
	}
}

再次执行,结果如下:

Thread-0,getNextId()=1
Thread-1,getNextId()=2
Thread-0,getNextId()=3
Thread-1,getNextId()=4
Thread-0,getNextId()=5
Thread-1,getNextId()=6

小结:加上synchronized 关键字,数据正常了。

 

3、synchronized应用于代码块(对象锁是当前类的class对象)的场景,修改后的getNextId()方法如下:

public class IdTool {

	private static int currentIdValue = 0;
	
	public static  int getNextId(){
		synchronized(IdTool.class){			
			currentIdValue = currentIdValue+1;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return currentIdValue;
		}
	}
}

再次执行,结果如下:

Thread-0,getNextId()=1
Thread-1,getNextId()=2
Thread-0,getNextId()=3
Thread-1,getNextId()=4
Thread-0,getNextId()=5
Thread-1,getNextId()=6

小结:同步代码块也可以解决线程安全问题。

补充说明:synchronized(IdTool.class){}代码块等价于在public static synchronized ,加锁对象都是当前类对象。

4、synchronized应用于代码代码块(对象锁是第三方对象)的场景,修改后的getNextId()方法如下:

public class IdTool {

	private static int currentIdValue = 0;
	private static byte[] byteObj = new byte[0];
	
	public static  int getNextId(){
		synchronized(byteObj){			
			currentIdValue = currentIdValue+1;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return currentIdValue;
		}
	}
}

之所以用长度为0的字节数组,是因为,据说此种对象是最轻量级的对象,比Object obj = new Object();还要小。

再次执行,结果如下:

Thread-0,getNextId()=1
Thread-1,getNextId()=2
Thread-0,getNextId()=3
Thread-1,getNextId()=4
Thread-0,getNextId()=5
Thread-1,getNextId()=6

小结:当共享资源无法作为加锁对象时,可以使用第三方对象作为加锁对象。

5、synchronized应用于代码代码块(临时创建对象锁)的场景,修改后的getNextId()方法如下:

public class IdTool {

	private static int currentIdValue = 0;
	
	public static  int getNextId(){
		synchronized(new byte[0]){			
			currentIdValue = currentIdValue+1;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return currentIdValue;
		}
	}
}

再次执行,结果如下:

Thread-0,getNextId()=2
Thread-1,getNextId()=2
Thread-0,getNextId()=4
Thread-1,getNextId()=4
Thread-0,getNextId()=6
Thread-1,getNextId()=6

小结:每一个线程执行到达同步块时,都会请求对加锁对象加锁,由于此处的加锁对象是动态的,即,每个线程过来后,都会创建一个加锁对象,并为之加锁,所以,每个线程过来时,加锁都会成功,进而此种对象锁是无意义的,不能解决线程安全问题。

 

6、对象锁是类对象,并且该类中存在多个同步方法的场景。

6.1、新建公共资源类。

import java.util.Date;

public class PublicResource {

	public static synchronized void method1(){
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(new Date()+" "+Thread.currentThread().getName()+",执行method1");
	}
	
	public static synchronized void  method2(){
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(new Date()+" "+Thread.currentThread().getName()+",执行method2");
	}
}

 6.2、新建2个线程类

public class Thread1 extends Thread {

	@Override
	public void run() {
		PublicResource.method1();
	}	
}
public class Thread2 extends Thread {

	@Override
	public void run() {
		PublicResource.method2();
	}	
}

6.3、新建测试类

public class Test {

	public static void main(String[] args) {
		
		Thread1 t1 = new Thread1();
		Thread2 t2 = new Thread2();
		
		t1.start();
		t2.start();
	}

}

 6.4、执行测试类,结果如下:

Tue Mar 20 12:27:15 CST 2012 Thread-0,执行method1
Tue Mar 20 12:27:20 CST 2012 Thread-1,执行method2

小结:当同步方法共用一个对象做为加锁对象时,则这些同步方法中的任何一个方法正在执行时,其余同步方法都将不能被调用。如:同步方法a和同步方法b的加锁对象都是当前类对象,则线程A执行同步方法a时,线程B既不能执行同步方法a,也不能执行同步方法b。

 

归纳总结:

1、非静态方法用synchronized关键字修饰,等价于同步代码块中的synchronized(this),即以当前对象做为加锁对象。

2、静态方法用synchronized关键字修饰,等价于同步代码块中的synchronized(当前类.class),即以当前类class对象做为加锁对象。

3、一个对象,只能同时被加一个锁,当一个线程对一个对象加锁后,其余线程无法继续对该对象执行加锁操作,只有等待锁去除后,方能继续执行加锁操作。

4、当一个线程执行到同步代码块或者同步方法时,必须对相应的加锁对象加锁后,才能执行里面的代码。

5、当一个线程执行完同步代码块或者同步方法时,相应的加锁对象上的锁会自动去除。

6、加锁对象选择策略:选择共享资源作为加锁对象,当共享资源不方便作为加锁对象时,可以根据实际业务场景选择this、class、第三方对象,作为加锁对象。

http://huangqiqing123.iteye.com/admin/blogs/1458542

猜你喜欢

转载自huangqiqing123.iteye.com/blog/1458542