synchronized同步方法
当多个线程同时访问同一对象中的实例变量时,就会出现非线程安全性,从而导致脏读取,即所检索的数据发生更改。线程安全性意味着所获得的实例变量的值是同步的。
方法内的变量是线程安全的
方法中的变量是线程安全的。非线程安全问题存在于实例变量中。如果它是一个方法中的私有变量,就不会有非线程安全问题。实例如下:
1 class hasmethodprivatenum { 2 public void addi(string username){ 3 try { 4 int num=0; 5 if(username.equals("a")){ 6 num=100; 7 system.out.println("a set over"); 8 thread.sleep(2000); 9 }else{ 10 num=200; 11 system.out.println("b set over"); 12 } 13 system.out.println(username+" num = "+num); 14 } catch (interruptedexception e) { 15 e.printstacktrace(); 16 } 17 } 18 } 19 20 class threada extends thread { 21 private hasmethodprivatenum numref; 22 public threada(hasmethodprivatenum numref){ 23 super(); 24 this.numref=numref; 25 } 26 27 @override 28 public void run() { 29 super.run(); 30 numref.addi("a"); 31 } 32 } 33 34 class threadb extends thread { 35 private hasmethodprivatenum numref; 36 public threadb(hasmethodprivatenum numref){ 37 super(); 38 this.numref=numref; 39 } 40 41 @override 42 public void run() { 43 super.run(); 44 numref.addi("b"); 45 } 46 } 47 48 public class run { 49 public static void main(string[] args) { 50 hasmethodprivatenum numref=new hasmethodprivatenum(); 51 threada threada=new threada(numref); 52 threada.start(); 53 threadb threadb=new threadb(numref); 54 threadb.start(); 55 } 56 }
输出结果:
a set over b set over b num = 200 a num = 100
可以看出,该方法中的变量不存在非线性安全问题,并且是线程安全的。
实例变量非线程安全
实例变量是非线程安全的。如果多个线程联合访问对象中的实例变量,则可能出现非线程安全性问题。如果线程访问的对象中有多个实例变量,则运行结果可能会交叉,如果只有一个实例变量,则可能存在覆盖。在这种情况下,您需要将synchronized关键字添加到操作实例变量的方法中。当多个线程访问同一对象中的同步方法时,它必须是线程安全的。
修改上面的代码以将变量作为类中的成员变量放在第一类的addi()方法中:
1 class hasselfprivatenum { 2 private int num=0; 3 synchronized public void addi(string username){ 4 try { 5 if(username.equals("a")){ 6 num=100; 7 system.out.println("a set over"); 8 thread.sleep(2000); 9 }else{ 10 num=200; 11 system.out.println("b set over"); 12 } 13 system.out.println(username+" num = "+num); 14 } catch (interruptedexception e) { 15 e.printstacktrace(); 16 } 17 } 18 }
测试结果如下:
1 a set over 2 b set over 3 b num = 200 4 a num = 200
可以发现,得到的结果是存在线程安全问题的。当为addi()方法加上synchronized关键字之后,测试结果如下:
1 a set over 2 a num = 100 3 b set over 4 b num = 200
可以发现,不存在线程安全问题了。
synchronized关键字获取所有对象锁的锁,而不是使用一段代码或方法作为锁。当多个线程访问同一个对象时,哪个线程首先使用关键字执行方法,哪个线程持有方法所属对象的锁,而其他线程只能等待。但是,如果多个线程访问多个对象,jvm将创建多个锁。
当对象分别具有同步方法a和异步方法b、线程a和线程b访问方法a和方法b时,线程a首先持有对象的锁,但是线程b可以异步调用对象的异步方法b。但是如果两个方法都是同步的,那么当a访问方法a时,它已经持有对象的lock锁。当b线程调用对象的另一个同步方法时,它也需要等待,即同步。示例代码如下:
1 class myobject { 2 synchronized public void methoda(){ 3 try { 4 system.out.println("begin methoda in thread: "+thread.currentthread().getname()); 5 thread.sleep(5000); 6 system.out.println("end methoda in time:"+system.currenttimemillis()); 7 } catch (interruptedexception e) { 8 e.printstacktrace(); 9 } 10 } 11 12 public void methodb(){ 13 try { 14 system.out.println("begin methodb in thread: "+thread.currentthread().getname()+" time:"+system.currenttimemillis()); 15 thread.sleep(5000); 16 system.out.println("end methodb"); 17 } catch (interruptedexception e) { 18 e.printstacktrace(); 19 } 20 } 21 22 } 23 24 class threada extends thread{ 25 private myobject object; 26 public threada(myobject object){ 27 super(); 28 this.object=object; 29 } 30 31 @override 32 public void run() { 33 super.run(); 34 object.methoda(); 35 } 36 } 37 38 class threadb extends thread{ 39 private myobject object; 40 public threadb(myobject object){ 41 super(); 42 this.object=object; 43 } 44 45 @override 46 public void run() { 47 super.run(); 48 object.methodb(); 49 } 50 } 51 52 public class run { 53 public static void main(string[] args) { 54 myobject object=new myobject(); 55 threada a=new threada(object); 56 a.setname("a"); 57 threadb b=new threadb(object); 58 b.setname("b"); 59 a.start(); 60 b.start(); 61 } 62 }
测试结果:
begin methoda in thread: a begin methodb in thread: b time:1544263806800 end methodb end methoda in time:1544263811800
如您所见,线程a首先获得对象对象的锁,但是线程b仍然异步调用异步方法。将同步关键字添加到multb()之后,测试结果如下:
begin methoda in thread: a end methoda in time:1544264023516 begin methodb in thread: b time:1544264023516 end methodb
可以看到,a线程先得到object的锁,b线程如果此时调用objcet中的同步方法需要等待。
脏读
同步关键字可用于调用相同方法时同步多个线程。虽然分配是同步的,但是当取值时,即当读取实例变量时,该值已被其他线程更改,可能会发生脏读取。因此,还需要同步方法来读取数据。
锁重入
同步锁重入:当使用synchronized时,当线程获得对象锁时,当再次请求对象锁时,它可以再次获得对象的锁。也就是说,他们可以再次获得自己的内部锁。当线程获取对象的锁时,不会释放锁,并且它希望获取对象的锁。如果不允许锁重新进入,就会发生死锁。示例代码:
1 class service { 2 synchronized public void service1(){ 3 system.out.println("service1"); 4 service2(); 5 } 6 7 synchronized public void service2(){ 8 system.out.println("service2"); 9 service3(); 10 } 11 12 synchronized public void service3(){ 13 system.out.println("service3"); 14 } 15 } 16 class mythread extends thread{ 17 @override 18 public void run() { 19 service service=new service(); 20 service.service1(); 21 } 22 } 23 public class run { 24 public static void main(string[] args) { 25 mythread t=new mythread(); 26 t.start(); 27 } 28 }
测试结果:
service1 service2 service3
父子继承环境中还支持可重入锁。当存在父子类继承关系时,子类可以通过“可重新引入的锁”调用父类的同步方法。示例代码如下:
class main { public int i=10; synchronized public void operateimainmethod(){ try { i--; system.out.println("main print i = "+i); thread.sleep(100); } catch (interruptedexception e) { e.printstacktrace(); } } } class sub extends main{ public synchronized void operateisubmethod() { try { while(i>0){ i--; system.out.println("sub print i="+i); thread.sleep(100); this.operateimainmethod(); } } catch (interruptedexception e) { e.printstacktrace(); } } } class mythread extends thread { @override public void run() { sub sub=new sub(); sub.operateisubmethod(); } } public class run { public static void main(string[] args) { mythread t=new mythread(); t.start(); } }
测试结果为:
sub print i=9 main print i = 8 sub print i=7 main print i = 6 sub print i=5 main print i = 4 sub print i=3 main print i = 2 sub print i=1 main print i = 0
当线程执行异常代码时,它所持有的锁会自动释放。
同步不具有继承性
如果父类的方法是同步方法,但是子类在不添加同步关键字的情况下重写方法,那么子类的方法在调用子类的方法时仍然不是同步方法,并且需要将同步关键字添加到子类的e方法。
上一篇: 韩国首尔最佳旅游季节?