一、准备工作:
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修饰符,修改后的代码如下:
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