以前写过synchronized的测试笔记,今天又发现synchronized的一些问题,继续学习。先把以前的贴出来:http://709002341.iteye.com/admin/blogs/2253666
已经做过的测试就不做了,今天只做用synchronized锁住String类型和锁住基本类型的测试。
其中8个基本类型没什么区别,就用Integer代替了。
首主程序的测试代码:
/** * main: * * @param args * void 返回类型 * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub long start = System.currentTimeMillis(); Thread1 t1 = new Thread1(1); Thread1 t2 = new Thread1(2); Thread1 t3 = new Thread1(3); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); long end = System.currentTimeMillis(); System.out.println("一共执行了:"+(end - start)); }
以下所有的测试程序都是这个测试代码测试。
首先第一个测试:
static class Thread1 extends Thread { private Object o = new Object(); private int i; private Integer j = new Integer(1); public Thread1(int i){ this.i = i; } public void run() { synchronized(j){ System.out.println("线程"+i+"在执行---------"); try { //睡2秒 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
结果:
线程1在执行--------- 线程2在执行--------- 线程3在执行--------- 一共执行了:2004
第二个测试:
static class Thread1 extends Thread { private Object o = new Object(); private int i; private Integer j = 1; public Thread1(int i){ this.i = i; } public void run() { synchronized(j){ System.out.println("线程"+i+"在执行---------"); try { //睡2秒 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
结果:
线程1在执行--------- 线程3在执行--------- 线程2在执行--------- 一共执行了:6008
这是因为用new创建出来的Integer对象在堆上,具体于某个对象,所以三个线程对象的j都是不同的。但是直接赋值创建的基础类型的对象实体是在常量池中,所以不论多少个线程对象,j指向的对象是同一位置的。
再看两个:
static class Thread1 extends Thread { private Object o = new Object(); private int i; private Integer j = 1111; public Thread1(int i){ this.i = i; } public void run() { synchronized(j){ System.out.println("线程"+i+"在执行---------"); try { //睡2秒 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } }
结果为:
线程1在执行--------- 线程2在执行--------- 线程3在执行--------- 一共执行了:2007
static class Thread1 extends Thread { private Object o = new Object(); private int i; private static Integer j = 1111; public Thread1(int i){ this.i = i; } public void run() { synchronized(j){ System.out.println("线程"+i+"在执行---------"); try { //睡2秒 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } }
结果为:
线程1在执行--------- 线程3在执行--------- 线程2在执行--------- 一共执行了:6004
这也佐证了这个观点:Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用常量池。
再看一个比较奇怪的:
static class Thread1 extends Thread { private Object o = new Object(); private int i; private static Integer j = 1; public Thread1(int i){ this.i = i; } public void run() { synchronized(j){ j = 2; // ---------------① System.out.println("线程"+i+"在执行---------"); try { //睡2秒 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } }
执行结果:
线程1在执行--------- 线程2在执行--------- 线程3在执行--------- 一共执行了:4007
这是因为三个对象创建的j的引用其实都是同一个,锁的是引用j指向的对象。所以第一次访问的时候锁的是常量池中的1,第二次和第三次访问锁的是常量池中的2,所以出现这种情况:第一个线程独立,第二个线程和第三个线程互斥。
锁住String类型和锁住Integer类型是类似的,因为String类型也保存在常量池中。
不同的是,String类型有intern方法。但是intern方法是将字符串放入常量池,而不改变引用的指向。所以下面代码:
static class Thread1 extends Thread { private Object o = new Object(); private int i; private static String j = new String("sssssssssssssssssss"); public Thread1(int i){ this.i = i; } public void run() { synchronized(j){ j.intern(); //------------① System.out.println("线程"+i+"在执行---------"); try { //睡2秒 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } }
结果为:
线程1在执行--------- 线程2在执行--------- 线程3在执行--------- 一共执行了:6005
但是如果①改为这样:j = "s";执行结果就是:
线程1在执行--------- 线程2在执行--------- 线程3在执行--------- 一共执行了:4006
这是因为j = "s";不止在常量池中创建一个字符串对象s,而且将引用j指向s。
另外我测试了一下集合:
static class Thread1 extends Thread { private Object o = new Object(); private int i; private static List<String> l = new ArrayList<String>(); public Thread1(int i){ this.i = i; } public void run() { synchronized(l){ l.add("sss"); System.out.println("线程"+i+"在执行---------"); try { //睡2秒 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } }
结果为:
线程1在执行--------- 线程3在执行--------- 线程2在执行--------- 一共执行了:6008
可知引用指向的对象没有变,虽然他使用了add方法。
最后总结一下(个人总结,大神请喷):
使用synchronized关键字,锁的是对象,在java中一切都是对象(基本类型除外)。
synchronized锁static方法的时候,锁的是整个类,可以认为锁住了方法区中类的那块区域。
synchronized锁普通方法的时候,锁的是当前方法所在的对象,可以认为是锁住了当前对象在堆中的位置。
synchronized锁方法块的时候,比如执行 synchronized(Object o),那么synchronized锁住的就是引用o指向的内存地址。如果o是基础类型中的Byte,Short,Integer,Long,Character并且范围在(-128,127)的时候(在常量池中保存的数据),锁住的就是该对象在常量池中的位置(多个Integer对象同一个位置,共享一把锁)
如何判断两个synchronized是不是同一把锁? 只要synchronized(Object o1)和synchronized(Object o2)中,o1==o2返回true,就说明两个synchronized方法块使用的是同一把锁