全面解析Synchronized
2015-12-07 00:00
197 查看
摘要: 正确的使用synchronized关键字,避免多线程间的线程安全问题
在多线程的环境下,经常存在线程安全问题,这种问题产生的原因在于:该是原子操作的代码段被其他的线程切割,从而引起的数据混乱问题。在本篇博客中将讲述如何使用synchronized关键字保证代码段的原子操作。
synchronized关键字具有一下特征:
(1)如果对持有相同锁的synchronized方法或者代码块,同步执行(即排队,执行完一个,另一个才能执行)
(2)如果对持有不同锁的synchronized方法或者代码块,异步执行
(3)synchronized与非synchronized方法或者代码块,异步执行
下面通过synchronized方法来看一下上述的三个特征
首先定义一个操作类MyObject,在类中有三个方法init,alter,print,其中init,alter方法使用synchronized修饰,代码如下:
定义三个线程,分别对MyObject进行处理:
测试类如下:
分析:在测试类中,三个线程都是以实例化对象object1作为参数,那么三个类处理的都是object1对象,因为MyThreadA,MyThreadB处理的object1对象的同步方法,他们持有的监视器都是object1对象,因此两者应该是同步的,即排队执行,MyThreadC处理的是非synchronized方法,因此和另外两个线程是异步执行的。所以init方法,和alter方法是顺序执行(即一个执行完,另一个在执行,但是谁在前取决于谁先抢到object1的锁),print方法和init,alter方法是异步执行,可以在任意位置输出。
程序运行结果如下:
从本例中说明对持有相同锁的多个线程在执行synchronized方法时同步执行。synchronized方法与非synchronized方法异步执行。
下面修改测试类,观察如果线程处理的对象不是一个对象会出现什么情况:
分析:线程a操作的对象是object1,线程b操作的对象是object2。另一层意思,线程a执行init方法是获取的是object1对象的锁,线程b执行alter方法时获取的是object2对象的锁。虽然两个方法都是synchronized方法,但是两者想要获取的锁不同,因此也就不存在同步问题。
执行结果如下:
从结果中可以看出,线程0中init的操作被线程1的alter操作分隔了,说明两者异步执行。
从而证明了synchronized方法持有的是this对象锁。对持有不同锁对象的synchronized方法异步执行。
通过上面的实验证明了synchronized关键字的3个特征。
测试类如下:
分析:实例化操作类对象object1,创建两个线程a,b,a线程启动,执行init方法,线程a获取了object1对象的锁,在init方法方法中睡眠2s,执行init方法1s后启动线程b,b企图获得object1对象锁,由于被线程a占有,所以需要等待。2s过后init方法中调用alter方法,需要获取object1对象的锁,现在线程a已经获得了object1对象的锁,可以直接进入alter方法,这就叫课重入性。执行完alter方法之后,线程a释放锁,线程b再执行alter方法。
输出结果如下:
(2)子类调用父类的同步方法
创建父类:
创建子类:
测试类:
分析:启动线程a,a获取了student的锁,执行study方法,在study方法中调用父类的eat方法,不需要重新获取锁即可进入同步方法。
执行结果如下:
synchronized抛出异常,自动释放锁
修改操作类:
线程类使用上面的MyThreadA,MyThreadB,此处不再列出
测试类如下:
分析:线程a执行首先获取object对象锁,执行init方法,执行完init时抛出异常,如果线程a不释放锁,那么线程b就不会执行。只要线程b执行了,就说明抛出异常,线程锁释放。
运行结果如下:
说明线程a抛出异常时,释放了线程锁
synchronized方法不具有继承性
不具有继承性主要体现在方法的重写上,对父类的同步方法进行重写,如果不加synchronized关键字就不是同步方法
通过程序验证上述结论:
定义两个线程:
测试类:
执行结果如下:
分析:从运行结果中可以看出两个方法并不是同步输出的,因此重写的eat方法不是同步方法。
synchronized语句块
举例说明synchronized方法的性能问题。
定义操作类:
定义线程类:
测试类:
分析:在测试类中定义了两个线程实例a,b 这两个线程都以object实例作为参数,对object进行操作。首先线程a执行,输出线程a的开始执行时间,执行object对象的init方法,init方法是同步方法。在执行init的同时,线程b也启动了,输出线程b的开始执行时间,这时线程b企图获取object对象的锁,由于现在object的锁被线程a占用,所以必须等待线程a执行完init方法之后才会获得。线程a休眠2秒,这2s主要是模拟长时间的操作(假设就是从服务端获取数据),给成员变量赋值,打印两个成员变量,运行完成,释放锁。输出线程a的结束时间,线程b执行object对象的init的放,进行一些列的操作。
运行结果如下:
查看这两个线程都完成操作所用的总时间就是Thread-0的结束时间减去Thread-1开始运行时间:1449471258365-1449471254353 = 4012 花费了大约4s的时间。但是想服务端获取数据完全可以异步去执行,这样就可以减少执行时间了。
修改操作对象MyObject的代码,使用synchronized语句块
其线程类与测试类代码不变,分析程序:
在测试类中创建两个线程实例a,b 线程a与线程b同时执行object的init方法,由于此方法是非synchronized方法,因此,两只异步执行,(两者谁先执行到synchronized语句块不确定)假设线程a先执行到synchronized语句块,线程a获得了进入synchronized语句块的锁,当线程b执行到synchronized语句块时,尝试获取锁,但要得到线程a执行完synchronized语句块,线程a执行完synchronized语句块后释放锁,线程b获取锁执行synchronized语句块的内容。
执行结果如下:
从执行结果中可以看出,执行的总时间大约为2s,相比于使用synchronized方法快了很多。程序的性能得到了提升
总结:synchronized语句块就是将同步的内容最小化,使需要同步的信息放到synchronized语句块中同步执行,不需要同步的信息放到synchronized语句块外异步执行。
下面比较一下this,synchronized方法,class作为锁的区别:
首先synchronized(this){ } 使用的锁对象就是操作类本身,不如上例中在线程类中调用的是object.init()方法,说明init方法中的this指的就是object对象自己,因此使用this作为锁和synchronized方法没有本质的区别,区别只在于synchronized(this){ }可以将同步的范围缩小,是同步的内容更加精确,提高程序的性能。
sychronized(MyObject.class){ },是将class对象作为锁,同一个类的实例都只有一个class文件,class文件是唯一的,也就是说如果使用class作为锁,即使调用的是此类不同实例对象的方法,都会呈现出同步的效果。
举例说明:
定义操作类MyObject
定义线程类:
测试类代码如下:
分析:由于在MyObject类中使用的是this作为锁,在测试类中定义了两个MyObject对象object1,object2,实例化线程时使用两个不同的对象作为参数。线程a调用的是object1.init()方法,线程b调用的是object2.init()方法。两个线程在执行object.init()方法时,this代表的就不是一个对象,所以是异步执行。
运行结果如下:
下面修改MyObject操作类的代码:将synchronized(this){ } 改为synchronized(MyObject.class){ }
分析:两个线程a,b 线程a执行的是object1.init()方法,线程b执行的object2.init()方法,在执行到synchronized(MyObject.class){ }时,谁先执行到,谁就获得此类文件的class锁,由于object1和object2的class文件是同一个,因此两个线程在synchronized语句块处同步执行。
程序运行结果如下:
分析:两个线程共用一个object对象,object中lock是object的成员变量,也就是说两个线程共用一个锁。
因此两个线程在synchronized语句块处同步执行。
运行结果如下:
修改操作类MyObject代码
分析:在init方法中str是一个局部变量,在方法内部局部变量都有自己的临时空间,各个方法互不干扰,按常理来说,两个线程a,b应该想获取的锁不是同一个锁,但是这里是String类型,String类型的内容是放在常量池中的,在本程序中,str虽然是方法内的局部变量,但在不同的方法中局部变量都是指向的同一个对象,因此表示的是同一个锁。
因此不建议使用字符串作为锁对象
程序运行结果如下:
静态同步方法
synchronized方法对应synchronized(this){ }语句块,同样的synchronized static 方法对应synchronized(class){ }语句块
分析:两个线程虽然执行的是不同对象的init方法,但是init方法是static的synchronized方法,两个对象的class文件是同一个,因此两个线程同步执行(与synchronized(MyObject.class){ }一个效果),运行结果如下
定义两个线程类:
测试类如下:
运行结果如下:
从运行结果可以看出,class锁与对象锁不是同一个锁,两者异步执行
调用多个synchronized方法的脏读问题
如果在程序中连续调用几个synchronized方法,其中存在分支判断就会出现脏读的问题,下面举例说明:
定义一个只能存一个数据的对象MyObject,判断当数据的个数<1时添加元素,其中获取元素个数,添加元素都为synchronized方法。
定义操作类:
定义线程类:
测试类如下:
分析:两个线程a,b在执行时,线程a执行完size( )执行add()方法前释放锁,此时,线程b获取锁执行了size()方法,两个线程同时进入了if(object.size()<1)的语句块,两个线程都可以往object对象中添加数据,所以添加了两条数据,然而我们定义的确是只能保存一个数据的对象。因此程序出现了问题。
程序运行结果如下:
修改程序将线程类的run方法中使用synchronized语句块。
修改线程类:
程序运行结果:
锁对象的改变
在程序的运行过程中,锁对象有可能会发生变化,当锁对象发生变化时会出现什么情况呢?
定义操作类:
定义线程类:
定义测试类:
分析:线程a,b执行同一个对象的object.init( )方法,两者谁先执行到synchronized(str)是不确定的,假设线程a先到,那么a获取到str对象锁,执行synchronized语句块,接着修改了锁对象str = "hehhe",此时线程b执行到语句块synchronized(str),这里str执行的是hehhe了,不在是hahah,两者不是同一个锁,因此会异步执行
执行结果:
修改操作类MyObject对象的代码:
分析:线程a,b执行同一个对象的object.init( )方法,两者谁先执行到synchronized(str)是不确定的,假设线程a先到,线程a获取了str的锁,然后休眠2s,在执行str="hehhe"之前,线程b执行到synchronized(str),两者抢的是同一个对象的锁,因此会同步执行。
运行结果如下;
总结:当线程运行到synchronized语句块是,锁对象是哪一个对象,就会保持哪一个对象的锁。
在多线程的环境下,经常存在线程安全问题,这种问题产生的原因在于:该是原子操作的代码段被其他的线程切割,从而引起的数据混乱问题。在本篇博客中将讲述如何使用synchronized关键字保证代码段的原子操作。
synchronized关键字
不管synchronized以何种方式使用,都会对一个对象加锁,这个对象也就是所谓的监视器synchronized关键字具有一下特征:
(1)如果对持有相同锁的synchronized方法或者代码块,同步执行(即排队,执行完一个,另一个才能执行)
(2)如果对持有不同锁的synchronized方法或者代码块,异步执行
(3)synchronized与非synchronized方法或者代码块,异步执行
synchronized方法
使用synchronized来修饰方法(非static方法),其实就是this对象加锁下面通过synchronized方法来看一下上述的三个特征
首先定义一个操作类MyObject,在类中有三个方法init,alter,print,其中init,alter方法使用synchronized修饰,代码如下:
package com.feng.example; public class MyObject { synchronized public void init() { try { System.out.println(Thread.currentThread().getName()+"init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"init end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } synchronized public void alter() { try { System.out.println(Thread.currentThread().getName()+"alter begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"alter end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void printInfo() { try { System.out.println(Thread.currentThread().getName()+"print begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"print end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
定义三个线程,分别对MyObject进行处理:
package com.feng.example; public class MyThreadA extends Thread{ private MyObject object; public MyThreadA(MyObject object) { this.object = object; } @Override public void run() { object.init(); } }
package com.feng.example; public class MyThreadB extends Thread{ private MyObject object; public MyThreadB(MyObject object) { this.object = object; } @Override public void run() { object.alter(); } }
package com.feng.example; public class MyThreadC extends Thread{ private MyObject object; public MyThreadC(MyObject object) { this.object = object; } @Override public void run() { object.printInfo(); } }
测试类如下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object1 = new MyObject(); MyObject object2 = new MyObject(); Thread a = new MyThreadA(object1); Thread b = new MyThreadB(object1); Thread c = new MyThreadC(object1); a.start(); b.start(); c.start(); } }
分析:在测试类中,三个线程都是以实例化对象object1作为参数,那么三个类处理的都是object1对象,因为MyThreadA,MyThreadB处理的object1对象的同步方法,他们持有的监视器都是object1对象,因此两者应该是同步的,即排队执行,MyThreadC处理的是非synchronized方法,因此和另外两个线程是异步执行的。所以init方法,和alter方法是顺序执行(即一个执行完,另一个在执行,但是谁在前取决于谁先抢到object1的锁),print方法和init,alter方法是异步执行,可以在任意位置输出。
程序运行结果如下:
Thread-2print begin Thread-0init begin Thread-2print end Thread-0init end Thread-1alter begin Thread-1alter end
从本例中说明对持有相同锁的多个线程在执行synchronized方法时同步执行。synchronized方法与非synchronized方法异步执行。
下面修改测试类,观察如果线程处理的对象不是一个对象会出现什么情况:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object1 = new MyObject(); MyObject object2 = new MyObject(); Thread a = new MyThreadA(object1); Thread b = new MyThreadB(object2); a.start(); b.start(); } }
分析:线程a操作的对象是object1,线程b操作的对象是object2。另一层意思,线程a执行init方法是获取的是object1对象的锁,线程b执行alter方法时获取的是object2对象的锁。虽然两个方法都是synchronized方法,但是两者想要获取的锁不同,因此也就不存在同步问题。
执行结果如下:
Thread-0init begin Thread-1alter begin Thread-0init end Thread-1alter end
从结果中可以看出,线程0中init的操作被线程1的alter操作分隔了,说明两者异步执行。
从而证明了synchronized方法持有的是this对象锁。对持有不同锁对象的synchronized方法异步执行。
通过上面的实验证明了synchronized关键字的3个特征。
synchronized的可重入性
(1)同步方法中调用其他持有相同监视器的同步方法
修改操作类,代码如下:package com.feng.example; public class MyObject { synchronized public void init() { try { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); alter(); System.out.println(Thread.currentThread().getName()+"====init end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } synchronized public void alter() { try { System.out.println(Thread.currentThread().getName()+"====alter begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====alter end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void printInfo() { try { System.out.println(Thread.currentThread().getName()+"====print begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====print end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
测试类如下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { try { MyObject object1 = new MyObject(); Thread a = new MyThreadA(object1); Thread b = new MyThreadB(object1); a.start(); Thread.sleep(1000); b.start(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:实例化操作类对象object1,创建两个线程a,b,a线程启动,执行init方法,线程a获取了object1对象的锁,在init方法方法中睡眠2s,执行init方法1s后启动线程b,b企图获得object1对象锁,由于被线程a占有,所以需要等待。2s过后init方法中调用alter方法,需要获取object1对象的锁,现在线程a已经获得了object1对象的锁,可以直接进入alter方法,这就叫课重入性。执行完alter方法之后,线程a释放锁,线程b再执行alter方法。
输出结果如下:
Thread-0====init begin Thread-0====alter begin Thread-0====alter end Thread-0====init end Thread-1====alter begin Thread-1====alter end
(2)子类调用父类的同步方法
创建父类:package com.feng.example; public class Person { synchronized public void eat() { System.out.println("person eat"); } }
创建子类:
package com.feng.example; public class Student extends Person{ synchronized public void study() { System.out.println("student study"); super.eat(); System.out.println("study end"); } }
测试类:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { Student student = new Student(); Thread a = new MyThread(student); a.start(); } }
分析:启动线程a,a获取了student的锁,执行study方法,在study方法中调用父类的eat方法,不需要重新获取锁即可进入同步方法。
执行结果如下:
student study person eat study end
synchronized抛出异常,自动释放锁
修改操作类:package com.feng.example; public class MyObject { synchronized public void init() { try { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); throw new InterruptedException(); } catch (InterruptedException e) { System.out.println("抛出异常"); // TODO Auto-generated catch block e.printStackTrace(); } } synchronized public void alter() { try { System.out.println(Thread.currentThread().getName()+"====alter begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====alter end"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
线程类使用上面的MyThreadA,MyThreadB,此处不再列出
测试类如下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { try { MyObject object = new MyObject(); Thread a = new MyThreadA(object); Thread b = new MyThreadB(object); a.start(); Thread.sleep(1000); //此处是要保证线程a先执行 b.start(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:线程a执行首先获取object对象锁,执行init方法,执行完init时抛出异常,如果线程a不释放锁,那么线程b就不会执行。只要线程b执行了,就说明抛出异常,线程锁释放。
运行结果如下:
Thread-0====init begin 抛出异常 java.lang.InterruptedException at com.feng.example.MyObject.init(MyObject.java:11) at com.feng.example.MyThreadA.run(MyThreadA.java:15) Thread-1====alter begin Thread-1====alter end
说明线程a抛出异常时,释放了线程锁
synchronized方法不具有继承性
不具有继承性主要体现在方法的重写上,对父类的同步方法进行重写,如果不加synchronized关键字就不是同步方法通过程序验证上述结论:
package com.feng.example; public class Person { synchronized public void eat() { System.out.println("person eat"); } }
package com.feng.example; public class Student extends Person{ public void eat() { try { System.out.println("student eat..."); Thread.sleep(4000); System.out.println("student eat end..."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } synchronized public void study() { try { System.out.println("student study..."); Thread.sleep(2000); System.out.println("student study end..."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
定义两个线程:
package com.feng.example; public class StudentThreadA extends Thread{ private Student student; public StudentThreadA(Student student) { this.student = student; } @Override public void run() { // TODO Auto-generated method stub super.run(); student.eat(); } }
package com.feng.example; public class StudentThreadB extends Thread{ private Student student; public StudentThreadB(Student student) { this.student = student; } @Override public void run() { // TODO Auto-generated method stub super.run(); student.study(); } }
测试类:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { Student student = new Student(); Thread a = new StudentThreadA(student); Thread b = new StudentThreadB(student); a.start(); b.start(); } }
执行结果如下:
student eat... student study... student study end... student eat end...
分析:从运行结果中可以看出两个方法并不是同步输出的,因此重写的eat方法不是同步方法。
synchronized语句块
synchronized方法的弊端
使用synchronized方法,存在明显的性能问题,在一个方法中可能只有几个语句需要同步,使用synchronized方法却使整个方法都同步了。执行速度肯定会慢,我们可以通过synchronized语句块来优化程序。举例说明synchronized方法的性能问题。
定义操作类:
package com.feng.example; public class MyObject { private String data1; private String data2; synchronized public void init() { try { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); data1 = "长时间的处理后从服务端获取的值data1"; //模拟从服务端取数据 data2 = "长时间的处理后从服务端获取的值data2"; System.out.println(data1); System.out.println(data2); System.out.println(Thread.currentThread().getName()+"====init end"); } catch (InterruptedException e) { System.out.println("抛出异常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
定义线程类:
package com.feng.example; public class MyThread extends Thread { private MyObject object; public MyThread(MyObject object) { this.object = object; } @Override public void run() { System.out.println(Thread.currentThread().getName()+":开始运行时间为:"+System.currentTimeMillis()); object.init(); System.out.println(Thread.currentThread().getName()+":结束时间为:"+System.currentTimeMillis()); } }
测试类:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread a = new MyThread(object); Thread b = new MyThread(object); a.start(); b.start(); } }
分析:在测试类中定义了两个线程实例a,b 这两个线程都以object实例作为参数,对object进行操作。首先线程a执行,输出线程a的开始执行时间,执行object对象的init方法,init方法是同步方法。在执行init的同时,线程b也启动了,输出线程b的开始执行时间,这时线程b企图获取object对象的锁,由于现在object的锁被线程a占用,所以必须等待线程a执行完init方法之后才会获得。线程a休眠2秒,这2s主要是模拟长时间的操作(假设就是从服务端获取数据),给成员变量赋值,打印两个成员变量,运行完成,释放锁。输出线程a的结束时间,线程b执行object对象的init的放,进行一些列的操作。
运行结果如下:
Thread-1:开始运行时间为:1449471254353 Thread-1====init begin Thread-0:开始运行时间为:1449471254366 长时间的处理后从服务端获取的值data1 长时间的处理后从服务端获取的值data2 Thread-1====init end Thread-1:结束时间为:1449471256364 Thread-0====init begin 长时间的处理后从服务端获取的值data1 长时间的处理后从服务端获取的值data2 Thread-0====init end Thread-0:结束时间为:1449471258365
查看这两个线程都完成操作所用的总时间就是Thread-0的结束时间减去Thread-1开始运行时间:1449471258365-1449471254353 = 4012 花费了大约4s的时间。但是想服务端获取数据完全可以异步去执行,这样就可以减少执行时间了。
修改操作对象MyObject的代码,使用synchronized语句块
package com.feng.example; public class MyObject { private String data1; private String data2; public void init() { try { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); String data1Temp = "长时间的处理后从服务端获取的值data1"+Thread.currentThread().getName(); //模拟从服务端取数据 String data2Temp = "长时间的处理后从服务端获取的值data2"+Thread.currentThread().getName(); synchronized(this) { data1 = data1Temp; data2 = data2Temp; } System.out.println(data1); System.out.println(data2); System.out.println(Thread.currentThread().getName()+"====init end"); } catch (InterruptedException e) { System.out.println("抛出异常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
其线程类与测试类代码不变,分析程序:
在测试类中创建两个线程实例a,b 线程a与线程b同时执行object的init方法,由于此方法是非synchronized方法,因此,两只异步执行,(两者谁先执行到synchronized语句块不确定)假设线程a先执行到synchronized语句块,线程a获得了进入synchronized语句块的锁,当线程b执行到synchronized语句块时,尝试获取锁,但要得到线程a执行完synchronized语句块,线程a执行完synchronized语句块后释放锁,线程b获取锁执行synchronized语句块的内容。
执行结果如下:
Thread-1:开始运行时间为:1449472151572 Thread-1====init begin Thread-0:开始运行时间为:1449472151577 Thread-0====init begin 长时间的处理后从服务端获取的值data1Thread-1 长时间的处理后从服务端获取的值data2Thread-1 Thread-1====init end Thread-1:结束时间为:1449472153572 长时间的处理后从服务端获取的值data1Thread-0 长时间的处理后从服务端获取的值data2Thread-0 Thread-0====init end Thread-0:结束时间为:1449472153577
从执行结果中可以看出,执行的总时间大约为2s,相比于使用synchronized方法快了很多。程序的性能得到了提升
总结:synchronized语句块就是将同步的内容最小化,使需要同步的信息放到synchronized语句块中同步执行,不需要同步的信息放到synchronized语句块外异步执行。
synchronized语句块的锁对象,this,synchronized方法,class的区别
synchronized语句块的锁对象可以是任意的对象,不管是什么对象只要记住文章开头synchronized关机字的特征即可。下面比较一下this,synchronized方法,class作为锁的区别:
首先synchronized(this){ } 使用的锁对象就是操作类本身,不如上例中在线程类中调用的是object.init()方法,说明init方法中的this指的就是object对象自己,因此使用this作为锁和synchronized方法没有本质的区别,区别只在于synchronized(this){ }可以将同步的范围缩小,是同步的内容更加精确,提高程序的性能。
sychronized(MyObject.class){ },是将class对象作为锁,同一个类的实例都只有一个class文件,class文件是唯一的,也就是说如果使用class作为锁,即使调用的是此类不同实例对象的方法,都会呈现出同步的效果。
举例说明:
定义操作类MyObject
package com.feng.example; public class MyObject { public void init() { try { synchronized(this) { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====init end"); } } catch (InterruptedException e) { System.out.println("抛出异常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
定义线程类:
package com.feng.example; public class MyThread extends Thread { private MyObject object; public MyThread(MyObject object) { this.object = object; } @Override public void run() { object.init(); } }
测试类代码如下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object1 = new MyObject(); MyObject object2 = new MyObject(); Thread a = new MyThread(object1); Thread b = new MyThread(object2); a.start(); b.start(); } }
分析:由于在MyObject类中使用的是this作为锁,在测试类中定义了两个MyObject对象object1,object2,实例化线程时使用两个不同的对象作为参数。线程a调用的是object1.init()方法,线程b调用的是object2.init()方法。两个线程在执行object.init()方法时,this代表的就不是一个对象,所以是异步执行。
运行结果如下:
Thread-0====init begin Thread-1====init begin Thread-0====init end Thread-1====init end
下面修改MyObject操作类的代码:将synchronized(this){ } 改为synchronized(MyObject.class){ }
package com.feng.example; public class MyObject { public void init() { try { synchronized(MyObject.class) { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====init end"); } } catch (InterruptedException e) { System.out.println("抛出异常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:两个线程a,b 线程a执行的是object1.init()方法,线程b执行的object2.init()方法,在执行到synchronized(MyObject.class){ }时,谁先执行到,谁就获得此类文件的class锁,由于object1和object2的class文件是同一个,因此两个线程在synchronized语句块处同步执行。
程序运行结果如下:
Thread-0====init begin Thread-0====init end Thread-1====init begin Thread-1====init end
synchronized语句块其他对象作为锁
只要是对象都可以作为锁,修改上例中的MyObject类:package com.feng.example; public class MyObject { private String lock = new String(); public void init() { try { synchronized(lock) { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====init end"); } } catch (InterruptedException e) { System.out.println("抛出异常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:两个线程共用一个object对象,object中lock是object的成员变量,也就是说两个线程共用一个锁。
因此两个线程在synchronized语句块处同步执行。
运行结果如下:
Thread-0====init begin Thread-1====init begin 长时间的处理后从服务端获取的值data1Thread-0 长时间的处理后从服务端获取的值data2Thread-0 Thread-0====init end 长时间的处理后从服务端获取的值data1Thread-1 长时间的处理后从服务端获取的值data2Thread-1 Thread-1====init end
特殊情况,字符串作为锁对象
字符串一般是存放在常量池中的,如果两个字符串表示的内容一样,两个字符串指向的是同一个对象。举例说明:修改操作类MyObject代码
package com.feng.example; public class MyObject { public void init() { String str = "我是锁"; try { synchronized(str) { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====init end"); } } catch (InterruptedException e) { System.out.println("抛出异常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:在init方法中str是一个局部变量,在方法内部局部变量都有自己的临时空间,各个方法互不干扰,按常理来说,两个线程a,b应该想获取的锁不是同一个锁,但是这里是String类型,String类型的内容是放在常量池中的,在本程序中,str虽然是方法内的局部变量,但在不同的方法中局部变量都是指向的同一个对象,因此表示的是同一个锁。
因此不建议使用字符串作为锁对象
程序运行结果如下:
Thread-0====init begin Thread-0====init end Thread-1====init begin Thread-1====init end
静态同步方法
synchronized方法对应synchronized(this){ }语句块,同样的synchronized static 方法对应synchronized(class){ }语句块静态同步方法的使用
举例说明:修改上述程序的操作类代码:package com.feng.example; public class MyObject { synchronized static public void init() { try { System.out.println(Thread.currentThread().getName()+"====init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"====init end"); } catch (InterruptedException e) { System.out.println("抛出异常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:两个线程虽然执行的是不同对象的init方法,但是init方法是static的synchronized方法,两个对象的class文件是同一个,因此两个线程同步执行(与synchronized(MyObject.class){ }一个效果),运行结果如下
Thread-0====init begin Thread-0====init end Thread-1====init begin Thread-1====init end
class锁与对象锁异步执行
定义操作类MyObjectpackage com.feng.example; public class MyObject { synchronized static public void init() { try { System.out.println(Thread.currentThread().getName() + "====init begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "====init end"); } catch (InterruptedException e) { System.out.println("抛出异常"); // TODO Auto-generated catch block e.printStackTrace(); } } synchronized public void print() { try { System.out.println(Thread.currentThread().getName() + "====print begin"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "====print end"); } catch (InterruptedException e) { System.out.println("抛出异常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
定义两个线程类:
package com.feng.example; public class MyThreadA extends Thread{ private MyObject object; public MyThreadA(MyObject object) { this.object = object; } @Override public void run() { object.init(); } }
package com.feng.example; public class MyThreadB extends Thread{ private MyObject object; public MyThreadB(MyObject object) { this.object = object; } @Override public void run() { object.print(); } }
测试类如下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread a = new MyThreadA(object); Thread b = new MyThreadB(object); a.start(); b.start(); } }
运行结果如下:
Thread-1====print begin Thread-0====init begin Thread-1====print end Thread-0====init end
从运行结果可以看出,class锁与对象锁不是同一个锁,两者异步执行
调用多个synchronized方法的脏读问题
如果在程序中连续调用几个synchronized方法,其中存在分支判断就会出现脏读的问题,下面举例说明:
定义一个只能存一个数据的对象MyObject,判断当数据的个数<1时添加元素,其中获取元素个数,添加元素都为synchronized方法。
定义操作类:
package com.feng.example; import java.util.ArrayList; import java.util.List; public class MyObject { private List<String> list = new ArrayList<String>(); synchronized public void add() { list.add("xu"); } synchronized public int size() { return list.size(); } }
定义线程类:
package com.feng.example; public class MyThread extends Thread { private MyObject object; public MyThread(MyObject object) { this.object = object; } @Override public void run() { try { //保证object中的list中只有一个数据 if(object.size() < 1) { Thread.sleep(2000); object.add(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
测试类如下:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread a = new MyThread(object); Thread b = new MyThread(object); a.start(); b.start(); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(object.size()); } }
分析:两个线程a,b在执行时,线程a执行完size( )执行add()方法前释放锁,此时,线程b获取锁执行了size()方法,两个线程同时进入了if(object.size()<1)的语句块,两个线程都可以往object对象中添加数据,所以添加了两条数据,然而我们定义的确是只能保存一个数据的对象。因此程序出现了问题。
程序运行结果如下:
2
修改程序将线程类的run方法中使用synchronized语句块。
修改线程类:
package com.feng.example; public class MyThread extends Thread { private MyObject object; public MyThread(MyObject object) { this.object = object; } @Override public void run() { try { synchronized(object) { //保证object中的list中只有一个数据 if(object.size() < 1) { Thread.sleep(2000); object.add(); } } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
程序运行结果:
1
锁对象的改变
在程序的运行过程中,锁对象有可能会发生变化,当锁对象发生变化时会出现什么情况呢?定义操作类:
package com.feng.example; public class MyObject { private String str = "hahah"; public void init() { try { synchronized(str) { System.out.println(Thread.currentThread().getName() + "====init begin"); str = "hehhe"; //锁对象发生了变化 Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "====init end"); } } catch (InterruptedException e) { System.out.println("抛出异常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
定义线程类:
package com.feng.example; public class MyThread extends Thread { private MyObject object; public MyThread(MyObject object) { this.object = object; } @Override public void run() { object.init(); } }
定义测试类:
package com.feng.example; public class ThreadTest { /** * @param args */ public static void main(String[] args) { MyObject object = new MyObject(); Thread a = new MyThread(object); Thread b = new MyThread(object); a.start(); b.start(); } }
分析:线程a,b执行同一个对象的object.init( )方法,两者谁先执行到synchronized(str)是不确定的,假设线程a先到,那么a获取到str对象锁,执行synchronized语句块,接着修改了锁对象str = "hehhe",此时线程b执行到语句块synchronized(str),这里str执行的是hehhe了,不在是hahah,两者不是同一个锁,因此会异步执行
执行结果:
Thread-1====init begin Thread-0====init begin Thread-1====init end Thread-0====init end
修改操作类MyObject对象的代码:
package com.feng.example; public class MyObject { private String str = "hahah"; public void init() { try { synchronized(str) { System.out.println(Thread.currentThread().getName() + "====init begin"); Thread.sleep(2000); str = "hehhe"; //锁对象发生了变化 System.out.println(Thread.currentThread().getName() + "====init end"); } } catch (InterruptedException e) { System.out.println("抛出异常"); // TODO Auto-generated catch block e.printStackTrace(); } } }
分析:线程a,b执行同一个对象的object.init( )方法,两者谁先执行到synchronized(str)是不确定的,假设线程a先到,线程a获取了str的锁,然后休眠2s,在执行str="hehhe"之前,线程b执行到synchronized(str),两者抢的是同一个对象的锁,因此会同步执行。
运行结果如下;
Thread-0====init begin Thread-0====init end Thread-1====init begin Thread-1====init end
总结:当线程运行到synchronized语句块是,锁对象是哪一个对象,就会保持哪一个对象的锁。
相关文章推荐
- Java中使用synchronized关键字实现简单同步操作示例
- 深入Synchronized和java.util.concurrent.locks.Lock的区别详解
- 举例讲解Java中synchronized关键字的用法
- 详解java中的synchronized关键字
- java基本教程之synchronized关键字 java多线程教程
- java多线程编程之使用Synchronized块同步方法
- java多线程编程之使用Synchronized关键字同步类方法
- java多线程编程之使用Synchronized块同步变量
- Java 中 synchronized的用法详解(四种用法)
- 深入理解java中的synchronized关键字
- 解析Java线程编程中的线程安全与synchronized的使用
- Java synchronized用法
- java中synchronized的用法详解
- Java之voliate, synchronized, AtomicInteger使用
- 多线程 Synchronized 的注意细节
- Java中的ReentrantLock和synchronized两种锁定机制的对比
- java synchronized详解
- 多线程 wait sleep synchronized
- 【Java基础】多线程中同步的两种解决方案
- synchronized,ReetrantLock与volatile(一)