java多线程(四)synchronized关键字修饰方法

先来为大家介绍一下java中锁的概念。java中的每个对象和每个类都有锁,而且是互斥锁,也就是说只能有一方占有,另一方只能等到对方释放后才能再占有锁。synchronized关键字就是基于java的对象和类的锁的。


一、修饰普通方法


下面来看一下下面这个例子,Trans这个类是在一行中打印当前的线程和0-24这25个数。MyThread这个类接收一个Trans类的对象,在run方法中不停的调用printNum方法。在main方法中,创建了一个对象和两个线程,这两个线程都使用同一个Trans对象。

[java]  view plain copy
  1. public class Trans {  
  2.   
  3.     public void printNum(int num){  
  4.         System.out.print(Thread.currentThread());//获取当前运行这个方法的类  
  5.         for(int i=0;i<25;i++){  
  6.             System.out.print(i+" ");  
  7.         }  
  8.         System.out.println();  
  9.     }  
  10. }  

[java]  view plain copy
  1. class MyThread implements Runnable {  
  2.     private Trans trans;  
  3.     private int num;  
  4.   
  5.     public MyThread(Trans trans, int num) {  
  6.         this.trans = trans;  
  7.         this.num = num;  
  8.     }  
  9.   
  10.     public void run() {  
  11.         while (true)  
  12.         {  
  13.             trans.printNum(num);  
  14.             try {  
  15.                 Thread.sleep(500);  
  16.             } catch (InterruptedException e) {  
  17.                 e.printStackTrace();  
  18.             }  
  19.         }  
  20.               
  21.     }  
  22. }  
  23.   
  24. public class Test {  
  25.   
  26.   
  27.     public static void main(String[] args) {  
  28.   
  29.         Trans t = new Trans();  
  30.         Thread a = new Thread(new MyThread(t, 1));  
  31.         Thread b = new Thread(new MyThread(t, 2));  
  32.   
  33.         a.start();  
  34.         b.start();  
  35.   
  36.     }  
  37.   
  38. }  

现在,两个线程会同时执行Trans的printNum方法,因为又是同一个对象的方法,所以一定会产生RaceCondition,我的结果如下:



可以看到,打印结果确实是不正确的。那么怎么用synchronized关键字避免这种情况呢?可以将printNum方法的声明改为下面这种:

[java]  view plain copy
  1. public synchronized void printNum(int num){  
  2.         System.out.print(Thread.currentThread());  
  3.         for(int i=0;i<25;i++){  
  4.             System.out.print(i+" ");  
  5.         }  
  6.         System.out.println();  
  7.     }  

这就是用synchronized关键字修饰方法,现在我们再来试一下程序,看看还会不会产生RaceCondition。果然不会再产生RaceCondition了,是不是比用条件锁方便多了,那么为什么这样就可以了呢?我们来分析一下:之前我们说过,synchronized关键字会获得锁,所以在这里synchronized关键字获得的锁就是传递给线程的那个Trans对象的锁,两个线程用的是同一个锁,所以当一个线程执行到synchronized修饰的方法时,这个线程就会获得Trans对象的锁,并且这个锁是互斥的,所以其他试图想要获得锁的线程都必须等待,直到这个线程释放了锁,从而起到了互斥的作用。


每个对象都有自己的锁,所以这里synchronized关键字起作用的原因就是因为两个线程用的是同一个对象,如果每个线程都有自己的Trans对象,那么上边的方法将不再适用,例如我们把Main方法改为下面这样:


[java]  view plain copy
  1. public static void main(String[] args) {  
  2.   
  3.         Trans t = new Trans();  
  4.         Trans t1 = new Trans();  
  5.         Thread a = new Thread(new MyThread(t, 1));  
  6.         Thread b = new Thread(new MyThread(t1, 2));  
  7.   
  8.         a.start();  
  9.         b.start();  
  10.   
  11.     }  

这里每个线程都有自己的Trans对象,每个线程获得的锁也是自己对象的锁,两个锁之间互不干扰,所以synchronized关键字不会起到作用,那么这时应该怎么用synchronized关键字呢?那就是使用synchronized关键字修饰代码块,这种用法我们下篇博客再讲,这篇先把synchronized关键字修饰方法的用法讲清楚。


既然每个对象都只有一把互斥锁,那么同一个对象的多个synchronized关键字修饰的方法之间也是无法同时访问的。例如下面的例子:


[java]  view plain copy
  1. public class Trans {  
  2.   
  3.     public synchronized void printNum(int num) {  
  4.         while (true) {  
  5.             System.out.print(Thread.currentThread());  
  6.             for (int i = 0; i < 25; i++) {  
  7.                 System.out.print(i + " ");  
  8.             }  
  9.             System.out.println();  
  10.             try {  
  11.                 Thread.sleep(500);  
  12.             } catch (InterruptedException e) {  
  13.                 e.printStackTrace();  
  14.             }  
  15.               
  16.         }  
  17.     }  
  18.   
  19.     public synchronized void printNum1(int num) {  
  20.         System.out.print(Thread.currentThread());  
  21.         for (int i = 25; i > 0; i--) {  
  22.             System.out.print(i + " ");  
  23.         }  
  24.         System.out.println();  
  25.     }  
  26.   
  27.     public void printNum2(int num) {  
  28.         for (int i = 25; i > 0; i--) {  
  29.             System.out.print(num);  
  30.         }  
  31.         System.out.println();  
  32.     }  
  33. }  


[java]  view plain copy
  1. class MyThread implements Runnable {  
  2.     private Trans trans;  
  3.     private int num;  
  4.   
  5.     public MyThread(Trans trans, int num) {  
  6.         this.trans = trans;  
  7.         this.num = num;  
  8.     }  
  9.   
  10.     public void run() {  
  11.         trans.printNum(num);  
  12.     }  
  13. }  
  14.   
  15. class MyThread1 implements Runnable {  
  16.     private Trans trans;  
  17.     private int num;  
  18.   
  19.     public MyThread1(Trans trans, int num) {  
  20.         this.trans = trans;  
  21.         this.num = num;  
  22.     }  
  23.   
  24.     public void run() {  
  25.         while (true) {  
  26.             trans.printNum1(num);  
  27.             try {  
  28.                 Thread.sleep(500);  
  29.             } catch (InterruptedException e) {  
  30.                 e.printStackTrace();  
  31.             }  
  32.         }  
  33.     }  
  34. }  
  35.   
  36. class MyThread2 implements Runnable {  
  37.     private Trans trans;  
  38.     private int num;  
  39.   
  40.     public MyThread2(Trans trans, int num) {  
  41.         this.trans = trans;  
  42.         this.num = num;  
  43.     }  
  44.   
  45.     public void run() {  
  46.         while (true) {  
  47.             trans.printNum2(num);  
  48.             try {  
  49.                 Thread.sleep(500);  
  50.             } catch (InterruptedException e) {  
  51.                 e.printStackTrace();  
  52.             }  
  53.         }  
  54.     }  
  55. }  
  56.   
  57. public class Test {  
  58.   
  59.     public static void main(String[] args) {  
  60.   
  61.         Trans t = new Trans();  
  62.         Thread a = new Thread(new MyThread(t, 1));  
  63.         Thread b = new Thread(new MyThread1(t, 2));  
  64.         Thread c = new Thread(new MyThread2(t, 2));  
  65.   
  66.         a.start();  
  67.         b.start();  
  68.         c.start();  
  69.   
  70.     }  
  71.   
  72. }  


首先,Trans类增加了两个方法,一个方法其中一个方法也是用synchronized关键字修饰的方法,另一个方法是不用synchronized关键字修饰的,在Main方法中创建了三个线程,这个三个线程都是同一个Trans对象,分别调用三个不同的打印方法。第一个打印方法是一个死循环,一直在打印信息,又是synchronized修饰的方法,一旦获得了对象的锁就不会释放了,第二个方法也是synchronized关键字修饰的方法,这两个方法不能同时执行,如果第一个方法获得了锁,那么第二个方法将永远不能执行,第三个打印方法不是synchronized的,所以锁对它没有任何的影响,可以随时的执行。


下面是我的执行结果:



和我们想的一样,方法一和方法三交替执行,方法二不会执行。


二、修饰静态方法


我们知道静态 方法被整个类拥有,所有的类的对象共用这一个方法,那么synchronized关键字修饰static方法所获得的锁是这个类的锁,而不是这个对象的锁。那么一个类的锁和一个类的对象的锁有什么关系呢?

每个类都有一个互斥锁,每个类可能会有多个不同的对象,每个对象都有一个它自己的对象锁。自然,类的锁的管辖范围比对象的锁的管辖范围大,不同的对象之间的锁互不影响,但是他们都受类的锁的控制,如果一个类的锁被一个线程获得,那么这个类的所有的对象都不能访问需要获得类的锁的方法,但是可以访问不需要锁的方法和需要某个对象锁的方法。

例如下面这个例子,就可以很好的说明,static方法被synchronized修饰时,这个类的所有的对象都是锁的影响。

[java]  view plain copy
  1. public class Trans {  
  2.   
  3.     public static synchronized void printNum(int num){    
  4.         System.out.print(Thread.currentThread());    
  5.         for(int i=0;i<25;i++){    
  6.             System.out.print(i+" ");    
  7.         }    
  8.         System.out.println();    
  9.     }    
  10. }  



[java]  view plain copy
  1. class MyThread implements Runnable {    
  2.     private Trans trans;    
  3.     private int num;    
  4.     
  5.     public MyThread(Trans trans, int num) {    
  6.         this.trans = trans;    
  7.         this.num = num;    
  8.     }    
  9.     
  10.     public void run() {    
  11.         while (true)    
  12.         {    
  13.             trans.printNum(num);    
  14.             try {    
  15.                 Thread.sleep(500);    
  16.             } catch (InterruptedException e) {    
  17.                 e.printStackTrace();    
  18.             }    
  19.         }    
  20.                 
  21.     }    
  22. }    
  23.     
  24. public class Test {    
  25.     
  26.     
  27.     public static void main(String[] args) {    
  28.     
  29.         Trans t = new Trans();    
  30.         Trans t1 = new Trans();    
  31.         Thread a = new Thread(new MyThread(t, 1));    
  32.         Thread b = new Thread(new MyThread(t1, 2));    
  33.     
  34.         a.start();    
  35.         b.start();    
  36.     
  37.     }    
  38.     
  39. }  

一个线程访问static的synchronized方法时,其他的线程还可以访问不是static却被synchronized修饰的方法,例如:

[java]  view plain copy
  1. public class Trans {  
  2.   
  3.     public static synchronized void printNum(int num){    
  4.         System.out.print(Thread.currentThread());    
  5.         for(int i=0;i<25;i++){    
  6.             System.out.print(i+" ");    
  7.         }    
  8.         System.out.println();    
  9.     }  
  10.       
  11.     public synchronized void printNum1(int num){    
  12.         System.out.print(Thread.currentThread());    
  13.         for(int i=0;i<25;i++){    
  14.             System.out.print(2+" ");    
  15.         }    
  16.         System.out.println();    
  17.     }  
  18. }  

[java]  view plain copy
  1. class MyThread implements Runnable {    
  2.     private Trans trans;    
  3.     private int num;    
  4.     
  5.     public MyThread(Trans trans, int num) {    
  6.         this.trans = trans;    
  7.         this.num = num;    
  8.     }    
  9.     
  10.     public void run() {    
  11.         while (true)    
  12.         {    
  13.             trans.printNum(num);    
  14.             try {    
  15.                 Thread.sleep(500);    
  16.             } catch (InterruptedException e) {    
  17.                 e.printStackTrace();    
  18.             }    
  19.         }    
  20.                 
  21.     }    
  22. }    
  23.   
  24. class MyThread1 implements Runnable {    
  25.     private Trans trans;    
  26.     private int num;    
  27.     
  28.     public MyThread1(Trans trans, int num) {    
  29.         this.trans = trans;    
  30.         this.num = num;    
  31.     }    
  32.     
  33.     public void run() {    
  34.         while (true)    
  35.         {    
  36.             trans.printNum1(num);    
  37.             try {    
  38.                 Thread.sleep(500);    
  39.             } catch (InterruptedException e) {    
  40.                 e.printStackTrace();    
  41.             }    
  42.         }    
  43.                 
  44.     }    
  45. }  
  46.     
  47. public class Test {    
  48.     
  49.     
  50.     public static void main(String[] args) {    
  51.     
  52.         Trans t = new Trans();    
  53.         Thread a = new Thread(new MyThread(t, 1));    
  54.         Thread b = new Thread(new MyThread1(t, 2));    
  55.     
  56.         a.start();    
  57.         b.start();    
  58.     
  59.     }    
  60.     
  61. }  


了解了锁的概念和synchronized修饰方法的用法之后我们可以总结出,两个方法是不是互斥的关键是看两个方法取得的锁是不是互斥的,如果锁是互斥的,那么方法也是互斥访问的,如果锁不是互斥的,那么不同的锁之间是不会有什么影响的,所以这时方法是可以同时访问的。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值