JAVA多线程synchronized详解

Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,可以保证在同一时刻最多只有一个线程执行该段代码。java

  • 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程获得执行。另外一个线程必须等待当前线程执行完这个代码块之后才能执行该代码块。数据库

  • 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另外一个线程仍然能够访问该object中的非synchronized(this)同步代码块。数组

  • 尤为关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对object中全部其它synchronized(this)同步代码块的访问将被阻塞。安全

  • 第三个例子一样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就得到了这个object的对象锁。结果,其它线程对该object对象全部同步代码部分的访问都被暂时阻塞。多线程

  • 以上规则对其它对象锁一样适用.并发

     

 

举例说明(使用synchronized):函数

在编写一个类时,若是该类中的代码可能运行于多线程环境下,那么就要考虑同步的问题。在Java中内置了语言级的同步原语--synchronized,这也大大简化了Java中多线程同步的使用。咱们首先编写一个很是简单的多线程的程序,是模拟银行中的多个线程同时对同一个储蓄帐户进行存款、取款操做的。
在程序中咱们使用了一个简化版本的Account类,表明了一个银行帐户的信息。在主程序中咱们首先生成了10000个线程,而后启动它们,每个线程都对John的帐户进行存100元,而后立刻又取出100元。这样,对于John的帐户来讲,最终帐户的余额应该是仍是1000元才对。然而运行的结果却超出咱们的想像,首先来看看咱们的演示代码:oop

 1 package com.zb.notify;  2 
 3 /**
 4  * 内部类  5  * 模拟银行帐户,测试多线程环境下的存钱、取钱  6  *  7  * 做者: zhoubang  8  * 日期:2015年6月2日 上午10:09:52  9  */
10 class Account { 11     /**帐户金额*/
12     float amount; 13 
14     public Account(float amount) { 15         this.amount = amount; 16  } 17 
18     /**存钱*/
19     public void deposit(float amt) { 20         float tmp = amount; 21         tmp += amt; 22 
23         try { 24             /**模拟其它处理所须要的时间,好比存完钱,后台系统更新数据库字段值等*/
25             Thread.sleep(100); 26         } catch (InterruptedException e) { 27  } 28 
29         amount = tmp; 30  } 31 
32     /**取钱*/
33     public void withdraw(float amt) { 34         float tmp = amount; 35         tmp -= amt; 36 
37         try { 38             /**模拟其它处理所须要的时间,好比存完钱,后台系统更新数据库字段值等*/
39             Thread.sleep(100); 40         } catch (InterruptedException e) { 41  } 42 
43         amount = tmp; 44  } 45 
46     public float getBalance() { 47         return amount; 48  } 49 } 50 
51 /**
52  * 帐户存取金额 多线程测试 53  * 54  * 做者: zhoubang 55  * 日期:2015年6月2日 上午10:05:05 56  */
57 public class AccountTest { 58     /**模拟多个线程同时操做该帐户*/
59     private static int NUM_OF_THREAD = 10000; 60     
61     static Thread[] threads = new Thread[NUM_OF_THREAD]; 62 
63     public static void main(String[] args) { 64         /**为帐户初始化1000元*/
65         final Account acc = new Account(1000.0f); 66         
67         /**遍历线程,多个线程同时存取帐户金额:每个线程调用存钱的方法以后,当即调用取钱。*/
68         for (int i = 0; i < NUM_OF_THREAD; i++) { 69             threads[i] = new Thread(new Runnable() { 70                 public void run() { 71                     acc.deposit(100.0f); 72                     acc.withdraw(100.0f); 73  } 74  }); 75  threads[i].start(); 76  } 77 
78         /** 主线程等待全部线程运行结束 */
79         for (int i = 0; i < NUM_OF_THREAD; i++) { 80             try { 81                 threads[i].join();/**等待全部线程运行结束*/
82             } catch (InterruptedException e) { 83  } 84  } 85         
86         System.out.println("全部线程执行完毕,最终的帐户余额为:" + acc.getBalance()); 87  } 88 
89 }

注意,上面在Account的deposit和withdraw方法中之因此要把对amount的运算使用一个临时变量首先存储,sleep一段时间,而后,再赋值给amount,是为了模拟真实运行时的状况。由于在真实系统中,帐户信息确定是存储在持久媒介中,好比RDBMS中,此处的睡眠的时间至关于比较耗时的数据库操做,最后把临时变量tmp的值赋值给amount至关于把amount的改动写入数据库中。性能

运行AccountTest,结果以下(每一次结果都会不一样):测试

全部线程执行完毕,最终的帐户余额为:1500.0

全部线程执行完毕,最终的帐户余额为:1700.0

全部线程执行完毕,最终的帐户余额为:1300.0

。。。

 

为何会出现这样的问题?这就是多线程中的同步的问题。

在咱们的程序中,Account中的amount会同时被多个线程所访问,这就是一个竞争资源,一般称做竞态条件。

对于这样的多个线程共享的资源咱们必须进行同步,以免一个线程的改动被另外一个线程所覆盖。

在咱们这个程序中,Account中的amount是一个竞态条件,因此全部对amount的修改访问都要进行同步,

咱们将deposit()和withdraw()方法进行同步,修改成:

 1 package com.zb.notify;  2 
 3 /**
 4  * 内部类  5  * 模拟银行帐户,测试多线程环境下的存钱、取钱  6  *  7  * 做者: zhoubang  8  * 日期:2015年6月2日 上午10:09:52  9  */
10 class Account { 11     /**帐户金额*/
12     float amount; 13 
14     public Account(float amount) { 15         this.amount = amount; 16  } 17 
18     /**存钱*/
19     public synchronized void deposit(float amt) { 20         float tmp = amount; 21         tmp += amt; 22 
23         try { 24             /**模拟其它处理所须要的时间,好比存完钱,后台系统更新数据库字段值等*/
25             Thread.sleep(100); 26         } catch (InterruptedException e) { 27  } 28 
29         amount = tmp; 30  } 31 
32     /**取钱*/
33     public synchronized void withdraw(float amt) { 34         float tmp = amount; 35         tmp -= amt; 36 
37         try { 38             /**模拟其它处理所须要的时间,好比存完钱,后台系统更新数据库字段值等*/
39             Thread.sleep(100); 40         } catch (InterruptedException e) { 41  } 42 
43         amount = tmp; 44  } 45 
46     public float getBalance() { 47         return amount; 48  } 49 } 50 
51 /**
52  * 帐户存取金额 多线程测试 53  * 54  * 做者: zhoubang 55  * 日期:2015年6月2日 上午10:05:05 56  */
57 public class AccountTest { 58     /**模拟多个线程同时操做该帐户*/
59     private static int NUM_OF_THREAD = 10000; 60     
61     static Thread[] threads = new Thread[NUM_OF_THREAD]; 62 
63     public static void main(String[] args) { 64         /**为帐户初始化1000元*/
65         final Account acc = new Account(1000.0f); 66         
67         /**遍历线程,多个线程同时存取帐户金额:每个线程调用存钱的方法以后,当即调用取钱。*/
68         for (int i = 0; i < NUM_OF_THREAD; i++) { 69             threads[i] = new Thread(new Runnable() { 70                 public void run() { 71                     acc.deposit(100.0f); 72                     acc.withdraw(100.0f); 73  } 74  }); 75  threads[i].start(); 76  } 77 
78         /** 主线程等待全部线程运行结束 */
79         for (int i = 0; i < NUM_OF_THREAD; i++) { 80             try { 81                 threads[i].join();/**等待全部线程运行结束*/
82             } catch (InterruptedException e) { 83  } 84  } 85         
86         System.out.println("全部线程执行完毕,最终的帐户余额为:" + acc.getBalance()); 87  } 88 
89 }

此时,再运行,咱们就可以获得正确的结果了。

Account中的getBalance()也访问了amount,为何不对getBalance()同步呢?

由于getBalance()并不会修改amount的值,因此,同时多个线程对它访问不会形成数据的混乱。

 

同步加锁的是对象,而不是代码。
所以,若是你的类中有一个同步方法,这个方法能够被两个不一样的线程同时执行,只要每一个线程本身建立一个的该类的实例便可。

参考下面的代码:

 1 package com.zb.notify;  2 
 3 class Foo extends Thread {  4     private int val;  5 
 6     public Foo(int v) {  7         val = v;  8  }  9 
10     public synchronized void printVal(int v) { 11         while (true) 12  System.out.println(v); 13  } 14 
15     public void run() { 16  printVal(val); 17  } 18 } 19 
20 class SyncTest { 21     public static void main(String args[]) { 22         Foo f1 = new Foo(1); 23  f1.start(); 24         Foo f2 = new Foo(3); 25  f2.start(); 26  } 27 }

运行SyncTest产生的输出是1和3交叉的。

若是printVal是断面,你看到的输出只能是1或者只能是3而不能是二者同时出现。

程序运行的结果证实两个线程都在并发的执行printVal方法,即便该方法是同步的而且因为是一个无限循环而没有终止。

 

类的同步:

要实现真正的断面,你必须同步一个全局对象或者对类进行同步。下面的代码给出了一个这样的范例。

 1 package com.zb.notify;  2 
 3 class Foo extends Thread {  4     private int val;  5 
 6     public Foo(int v) {  7         val = v;  8  }  9 
10     public void printVal(int v) { 11         synchronized(Foo.class){ 12             while (true) 13  System.out.println(v); 14  } 15  } 16 
17     public void run() { 18  printVal(val); 19  } 20 }

上面的类再也不对个别的类实例同步而是对类进行同步。

对于类Foo而言,它只有惟一的类定义,两个线程在相同的锁上同步,所以只有一个线程能够执行printVal方法。

这个代码也能够经过对公共对象加锁。例如给Foo添加一个静态成员。两个方法均可以同步这个对象而达到线程安全。

 

下面给出一个参考实现,给出同步公共对象的两种一般方法:

一、

 1 package com.zb.notify;  2 
 3 class Foo extends Thread {  4     private int val;  5     private static Object lock = new Object();  6 
 7     public Foo(int v) {  8         val = v;  9  } 10 
11     public void printVal(int v) { 12         synchronized (lock) { 13             while (true) 14  System.out.println(v); 15  } 16  } 17 
18     public void run() { 19  printVal(val); 20  } 21 }

上面的这个例子比原文给出的例子要好一些,由于原文中的加锁是针对类定义的,一个类只能有一个类定义,而同步的通常原理是应该尽可能减少同步的粒度以到达更好的性能。这里给出的范例的同步粒度比原文的要小。

 

二、

 1 package com.zb.notify;  2 
 3 class Foo extends Thread {  4     private String name;  5     private String val;  6 
 7     public Foo(String name, String v) {  8         this.name = name;  9         val = v; 10  } 11 
12     public void printVal() { 13         synchronized (val) { 14             while (true) 15                 System.out.println(name + val); 16  } 17  } 18 
19     public void run() { 20  printVal(); 21  } 22 }
1 public class SyncMethodTest { 2     public static void main(String args[]) { 3         Foo f1 = new Foo("Foo 1:", "printVal"); 4  f1.start(); 5         Foo f2 = new Foo("Foo 2:", "printVal"); 6  f2.start(); 7  } 8 }

上面这个代码须要进行一些额外的说明,由于JVM有一种优化机制,由于String类型的对象是不可变的,所以当你使用""的形式引用字符串时,若是JVM发现内存已经有一个这样的对象,那么它就使用那个对象而再也不生成一个新的String对象,这样是为了减少内存的使用。

上面的main方法其实等同于:

1 public static void main(String args[]) { 2     String value="printVal"; 3     Foo f1 = new Foo("Foo 1:",value); 4  f1.start(); 5     Foo f2 = new Foo("Foo 2:",value); 6  f2.start(); 7 }

 

下面开始举第二个例子:

 1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程获得执行。

另外一个线程必须等待当前线程执行完这个代码块之后才能执行该代码块。

 1 package com.zb.notify;  2 
 3 public class Thread1 implements Runnable {  4     public void run() {  5         synchronized (this) {  6             for (int i = 0; i < 5; i++) {  7                 System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);  8  }  9  } 10  } 11 
12     public static void main(String[] args) { 13         Thread1 t1 = new Thread1(); 14         Thread ta = new Thread(t1, "A"); 15         Thread tb = new Thread(t1, "B"); 16  ta.start(); 17  tb.start(); 18  } 19 }

结果:

     A synchronized loop 0  
     A synchronized loop 1  
     A synchronized loop 2  
     A synchronized loop 3  
     A synchronized loop 4  
     B synchronized loop 0  
     B synchronized loop 1  
     B synchronized loop 2  
     B synchronized loop 3  
     B synchronized loop 4

2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另外一个线程仍然能够访问该object中的非synchronized(this)同步代码块。

 1 package com.zb.notify;  2 
 3 public class Thread2 {  4     public void m4t1() {  5         synchronized (this) {  6             int i = 5;  7             while (i-- > 0) {  8                 System.out.println(Thread.currentThread().getName() + " : " + i);  9                 try { 10                     Thread.sleep(500); 11                 } catch (InterruptedException ie) { 12  } 13  } 14  } 15  } 16 
17     public void m4t2() { 18         int i = 5; 19         while (i-- > 0) { 20             System.out.println(Thread.currentThread().getName() + " : " + i); 21             try { 22                 Thread.sleep(500); 23             } catch (InterruptedException ie) { 24  } 25  } 26  } 27 
28     public static void main(String[] args) { 29         final Thread2 myt2 = new Thread2(); 30         Thread t1 = new Thread(new Runnable() { 31             public void run() { 32  myt2.m4t1(); 33  } 34         }, "t1"); 35         Thread t2 = new Thread(new Runnable() { 36             public void run() { 37  myt2.m4t2(); 38  } 39         }, "t2"); 40  t1.start(); 41  t2.start(); 42  } 43 }

结果:

     t1 : 4  
     t2 : 4  
     t1 : 3  
     t2 : 3  
     t1 : 2  
     t2 : 2  
     t1 : 1  
     t2 : 1  
     t1 : 0  
     t2 : 0

3、尤为关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对object中全部其它synchronized(this)同步代码块的访问将被阻塞。

 1 // 修改Thread2.m4t2()方法:
 2     public void m4t2() {  3         synchronized (this) {  4             int i = 5;  5             while (i-- > 0) {  6  System.out  7                         .println(Thread.currentThread().getName() + " : " + i);  8                 try {  9                     Thread.sleep(500); 10                 } catch (InterruptedException ie) { 11  } 12  } 13  } 14     }

结果:

     t1 : 4  
     t1 : 3  
     t1 : 2  
     t1 : 1  
     t1 : 0  
     t2 : 4  
     t2 : 3  
     t2 : 2  
     t2 : 1  
     t2 : 0

4、第三个例子一样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就得到了这个object的对象锁。结果,其它线程对该object对象全部同步代码部分的访问都被暂时阻塞。

 1 // 修改Thread2.m4t2()方法以下:
 2     public synchronized void m4t2() {  3         int i = 5;  4         while (i-- > 0) {  5             System.out.println(Thread.currentThread().getName() + " : " + i);  6             try {  7                 Thread.sleep(500);  8             } catch (InterruptedException ie) {  9  } 10  } 11     }

结果:

     t1 : 4  
     t1 : 3  
     t1 : 2  
     t1 : 1  
     t1 : 0  
     t2 : 4  
     t2 : 3  
     t2 : 2  
     t2 : 1  
     t2 : 0

5、以上规则对其它对象锁一样适用:

 1 package com.zb.notify;  2 
 3 public class Thread3 {  4     class Inner {  5         private void m4t1() {  6             int i = 5;  7             while (i-- > 0) {  8                 System.out.println(Thread.currentThread().getName() + " : Inner.m4t1()=" + i);  9                 try { 10                     Thread.sleep(500); 11                 } catch (InterruptedException ie) { 12  } 13  } 14  } 15 
16         private void m4t2() { 17             int i = 5; 18             while (i-- > 0) { 19                 System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i); 20                 try { 21                     Thread.sleep(500); 22                 } catch (InterruptedException ie) { 23  } 24  } 25  } 26  } 27 
28     private void m4t1(Inner inner) { 29          synchronized(inner) { //使用对象锁 
30  inner.m4t1(); 31  } 32  } 33     private void m4t2(Inner inner) { 34  inner.m4t2(); 35  } 36 
37     public static void main(String[] args) { 38         final Foo myt3 = new Foo(); 39         final Inner inner = myt3.new Inner(); 40         Thread t1 = new Thread(new Runnable() { 41             public void run() { 42  myt3.m4t1(inner); 43  } 44         }, "t1"); 45         Thread t2 = new Thread(new Runnable() { 46             public void run() { 47  myt3.m4t2(inner); 48  } 49         }, "t2"); 50  t1.start(); 51  t2.start(); 52  } 53 }

结果:

尽管线程t1得到了对Inner的对象锁,但因为线程t2访问的是同一个Inner中的非同步部分。因此两个线程互不干扰。

     t1 : Inner.m4t1()=4  
     t2 : Inner.m4t2()=4  
     t1 : Inner.m4t1()=3  
     t2 : Inner.m4t2()=3  
     t1 : Inner.m4t1()=2  
     t2 : Inner.m4t2()=2  
     t1 : Inner.m4t1()=1  
     t2 : Inner.m4t2()=1  
     t1 : Inner.m4t1()=0  
     t2 : Inner.m4t2()=0

如今在Inner.m4t2()前面加上synchronized:

 1 private synchronized void m4t2() {  2             int i = 5;  3             while (i-- > 0) {  4                 System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);  5                 try {  6                     Thread.sleep(500);  7                 } catch (InterruptedException ie) {  8  }  9  } 10         }

结果:

尽管线程t1与t2访问了同一个Inner对象中两个绝不相关的部分,但由于t1先得到了对Inner的对象锁,因此t2对Inner.m4t2()的访问也被阻塞,由于m4t2()是Inner中的一个同步方法。

     t1 : Inner.m4t1()=4  
     t1 : Inner.m4t1()=3  
     t1 : Inner.m4t1()=2  
     t1 : Inner.m4t1()=1  
     t1 : Inner.m4t1()=0  
     t2 : Inner.m4t2()=4  
     t2 : Inner.m4t2()=3  
     t2 : Inner.m4t2()=2  
     t2 : Inner.m4t2()=1  
     t2 : Inner.m4t2()=0

 

java中synchronized用法:

    打个比方:一个object就像一个大房子,大门永远打开。房子里有 不少房间(也就是方法)。

    这些房间有上锁的(synchronized方法), 和不上锁之分(普通方法)。房门口放着一把钥匙(key),这把钥匙能够打开全部上锁的房间。

另外我把全部想调用该对象方法的线程比喻成想进入这房子某个 房间的人。全部的东西就这么多了,下面咱们看看这些东西之间如何做用的。

在此咱们先来明确一下咱们的前提条件。该对象至少有一个synchronized方法,不然这个key还有啥意义。固然也就不会有咱们的这个主题了。

一我的想进入某间上了锁的房间,他来到房子门口,看见钥匙在那儿(说明暂时尚未其余人要使用上锁的 房间)。因而他走上去拿到了钥匙

,而且按照本身 的计划使用那些房间。注意一点,他每次使用完一次上锁的房间后会立刻把钥匙还回去。即便他要连续使用两间上锁的房间,

中间他也要把钥匙还回去,再取回来。

    所以,普通状况下钥匙的使用原则是:“随用随借,用完即还。”

    这时其余人能够不受限制的使用那些不上锁的房间,一我的用一间能够,两我的用一间也能够,没限制。可是若是当某我的想要进入上锁的房

间,他就要跑到大门口去看看了。有钥匙固然拿了就走,没有的话,就只能等了。

    要是不少人在等这把钥匙,等钥匙还回来之后,谁会优先获得钥匙?Not guaranteed。象前面例子里那个想连续使用两个上锁房间的家伙,他

中间还钥匙的时候若是还有其余人在等钥匙,那么没有任何保证这家伙能再次拿到。 (JAVA规范在不少地方都明确说明不保证,象

Thread.sleep()休息后多久会返回运行,相同优先权的线程那个首先被执行,当要访问对象的锁被 释放后处于等待池的多个线程哪一个会优先得

到,等等。我想最终的决定权是在JVM,之因此不保证,就是由于JVM在作出上述决定的时候,毫不是简简单单根据 一个条件来作出判断,而是

根据不少条。而因为判断条件太多,若是说出来可能会影响JAVA的推广,也多是由于知识产权保护的缘由吧。SUN给了个不保证 就混过去了

。无可厚非。但我相信这些不肯定,并不是彻底不肯定。由于计算机这东西自己就是按指令运行的。即便看起来很随机的现象,其实都是有规律

可寻。学过 计算机的都知道,计算机里随机数的学名是伪随机数,是人运用必定的方法写出来的,看上去随机罢了。另外,或许是由于要想弄

的肯定太费事,也没多大意义,所 以不肯定就不肯定了吧。)

    再来看看同步代码块。和同步方法有小小的不一样。

    1.从尺寸上讲,同步代码块比同步方法小。你能够把同步代码块当作是没上锁房间里的一块用带锁的屏风隔开的空间。

    2.同步代码块还能够人为的指定得到某个其它对象的key。就像是指定用哪一把钥匙才能开这个屏风的锁,你能够用本房的钥匙;你也能够指定

用另外一个房子的钥匙才能开,这样的话,你要跑到另外一栋房子那儿把那个钥匙拿来,并用那个房子的钥匙来打开这个房子的带锁的屏风。

记住你得到的那另外一栋房子的钥匙,并不影响其余人进入那栋房子没有锁的房间。

    为何要使用同步代码块呢?我想应该是这样的:首先对程序来说同步的部分很影响运行效率,而一个方法一般是先建立一些局部变

量,再对这些变量作一些 操做,如运算,显示等等;而同步所覆盖的代码越多,对效率的影响就越严重。所以咱们一般尽可能缩小其影响范围。

如何作?同步代码块。咱们只把一个方法中该同 步的地方同步,好比运算。

    另外,同步代码块能够指定钥匙这一特色有个额外的好处,是能够在必定时期内霸占某个对象的key。还记得前面说过普通状况下钥

匙的使用原则吗。如今不是普通状况了。你所取得的那把钥匙不是永远不还,而是在退出同步代码块时才还。

    还用前面那个想连续用两个上锁房间的家伙打比方。怎样才能在用完一间之后,继续使用另外一间呢。用同步代码块吧。先建立另外

一个线程,作一个同步代码 块,把那个代码块的锁指向这个房子的钥匙。而后启动那个线程。只要你能在进入那个代码块时抓到这房子的钥匙

,你就能够一直保留到退出那个代码块。也就是说 你甚至能够对本房内全部上锁的房间遍历,甚至再sleep(10*60*1000),而房门口却还有

1000个线程在等这把钥匙呢。很过瘾吧。

    在此对sleep()方法和钥匙的关联性讲一下。一个线程在拿到key后,且没有完成同步的内容时,若是被强制sleep()了,那key还一

直在 它那儿。直到它再次运行,作完全部同步内容,才会归还key。记住,那家伙只是干活干累了,去休息一下,他并没干完他要干的事。为

了避免别人进入那个房间 把里面搞的一团糟,即便在睡觉的时候他也要把那惟一的钥匙戴在身上。

    最后,也许有人会问,为何要一把钥匙通开,而不是一个钥匙一个门呢?我想这纯粹是由于复杂性问题。一个钥匙一个门固然更

安全,可是会牵扯好多问题。钥匙 的产生,保管,得到,归还等等。其复杂性有可能随同步方法的增长呈几何级数增长,严重影响效率。这也

算是一个权衡的问题吧。为了增长一点点安全性,致使效 率大大下降,是多么不可取啊。

synchronized的一个简单例子:

 1 package com.zb.notify;  2 
 3 public class TextThread {  4 
 5     public static void main(String[] args) {  6         TxtThread tt = new TxtThread();  7         new Thread(tt).start();  8         new Thread(tt).start();  9         new Thread(tt).start(); 10         new Thread(tt).start(); 11  } 12 } 13 
14 class TxtThread implements Runnable { 15     int num = 100; 16     String str = new String(); 17 
18     public void run() { 19         synchronized (str) { 20             while (num > 0) { 21                 try { 22                     Thread.sleep(1); 23                 } catch (Exception e) { 24  e.getMessage(); 25  } 26                 System.out.println(Thread.currentThread().getName() + "this is " + num--); 27  } 28  } 29  } 30 }

上面的例子中为了制造一个时间差,也就是出错的机会,使用了Thread.sleep(10)

    Java对多线程的支持与同步机制深受你们的喜好,彷佛看起来使用了synchronized关键字就能够轻松地解决多线程共享数据同步问题。到底如

何?――还得对synchronized关键字的做用进行深刻了解才可定论。

    总的说来,synchronized关键字能够做为函数的修饰符,也可做为函数内的语句,也就是平时说的同步方法和同步语句块。

    若是再细的分类,synchronized可做用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

    在进一步阐述以前,咱们须要明确几点:

    A.不管synchronized关键字加在方法上仍是对象上,它取得的锁都是对象,而不是把一段代码或函数看成锁――并且同步方法极可能还会被其余线程的对象访问。

    B.每一个对象只有一个锁(lock)与之相关联。

    C.实现同步是要很大的系统开销做为代价的,甚至可能形成死锁,因此尽可能避免无谓的同步控制。

    接着来讨论synchronized用到不一样地方对代码产生的影响:

    假设P一、P2是同一个类的不一样对象,这个类中定义了如下几种状况的同步块或同步方法,P一、P2就均可以调用它们。

1. 把synchronized看成函数修饰符时,示例代码以下:

1 public synchronized void methodAAA() 2  { 3         //….
4     }

这也就是同步方法,那这时synchronized锁定的是哪一个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不一样的线程中

执行这个同步方法时,它们之间会造成互斥,达到同步的效果。可是这个对象所属的Class所产生的另外一对象P2却能够任意调用这个被加了synchronized关键字的方法。

上边的示例代码等同于以下代码:

1 public void methodAAA(){ 2     synchronized (this)// (1)
3  { 4        //
5  } 6 }

(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized做用于object reference。那个

拿到了P1对象锁的线程,才能够调用P1的同步方法,而对P2而言,P1这个锁与它绝不相干,程序也可能在这种情形下摆脱同步机制的控制,造

成数据混乱。

2.同步块,示例代码以下:

1 public void method3(SomeObject so){ 2     synchronized(so){ 3        //
4  } 5 }

这时,锁就是so这个对象,谁拿到这个锁谁就能够运行它所控制的那段代码。当有一个明确的对象做为锁时,就能够这样写程序,但当没有明

确的对象做为锁,只是想让一段代码同步时,能够建立一个特殊的instance变量(它得是一个对象)来充当锁:

1 class Foo implements Runnable{ 2     private byte[] lock = new byte[0]; // 特殊的instance变量
3     Public void methodA() { 4        synchronized(lock) { 5            //
6  } 7  } 8     //
9 }

注:零长度的byte数组对象建立起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操做码,而Object lock = new Object()则须要7行操做码。

3.将synchronized做用于static 函数,示例代码以下:

1 class Foo{ 2     public synchronized static void methodAAA() // 同步的static 函数 
3  { 4         //
5  } 6     public void methodBBB() { 7        synchronized(Foo.class);// class literal(类名称字面常量)
8  } 9 }

代码中的methodBBB()方法是把class literal做为锁的状况,它和同步的static函数产生的效果是同样的,取得的锁很特别,是当前调用这

个方法的对象所属的类(Class,而再也不是由这个Class产生的某个具体对象了)。

《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于做同步锁还不同,不能用P1.getClass()来达到锁这个Class的

目的。P1指的是由Foo类产生的对象。

能够推断:若是一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj

在多线程中分别访问A和B两个方法时,不会构成同步,由于它们的锁都不同。B方法的锁是Obj这个对象,而A的锁是Obj所属的那个Class。


小结以下:

搞清楚synchronized锁定的是哪一个对象,就能帮助咱们设计更安全的多线程程序。

还有一些技巧可让咱们对共享资源的同步访问更加安全:

1. 定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。若是将变量定义为public,对象在外界能够

绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。

2. 若是instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,由于当外界对象经过get方法拿到这个instance对象

的引用后,又将其指向另外一个对象,那么这个private变量也就变了,岂不是很危险。 这个时候就须要将get方法也加上synchronized同步,并

且,只返回这个private对象的clone()――这样,调用端获得的就是对象副本的引用了

相关文章
相关标签/搜索