多线程同步的方法5种

来源:互联网 发布:淘宝备份的装修在哪里 编辑:程序博客网 时间:2024/06/06 02:08

一、引言

前几天面试,被大师虐残了,好多基础知识必须得重新拿起来啊。闲话不多说,进入正题。

二、为什么要线程同步

因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题。

三、不同步时的代码

Bank.Java

[java] view plain copy
  1. package threadTest;  
  2.   
  3. /** 
  4.  * @author ww 
  5.  * 
  6.  */  
  7. public class Bank {  
  8.   
  9.     private int count =0;//账户余额  
  10.       
  11.     //存钱  
  12.     public  void addMoney(int money){  
  13.         count +=money;  
  14.         System.out.println(System.currentTimeMillis()+"存进:"+money);  
  15.     }  
  16.       
  17.     //取钱  
  18.     public  void subMoney(int money){  
  19.         if(count-money < 0){  
  20.             System.out.println("余额不足");  
  21.             return;  
  22.         }  
  23.         count -=money;  
  24.         System.out.println(+System.currentTimeMillis()+"取出:"+money);  
  25.     }  
  26.       
  27.     //查询  
  28.     public void lookMoney(){  
  29.         System.out.println("账户余额:"+count);  
  30.     }  
  31. }  


SyncThreadTest.java
[java] view plain copy
  1. package threadTest;  
  2.   
  3.   
  4. public class SyncThreadTest {  
  5.   
  6.     public static void main(String args[]){  
  7.         final Bank bank=new Bank();  
  8.           
  9.         Thread tadd=new Thread(new Runnable() {  
  10.               
  11.             @Override  
  12.             public void run() {  
  13.                 // TODO Auto-generated method stub  
  14.                 while(true){  
  15.                     try {  
  16.                         Thread.sleep(1000);  
  17.                     } catch (InterruptedException e) {  
  18.                         // TODO Auto-generated catch block  
  19.                         e.printStackTrace();  
  20.                     }  
  21.                     bank.addMoney(100);  
  22.                     bank.lookMoney();  
  23.                     System.out.println("\n");  
  24.                       
  25.                 }  
  26.             }  
  27.         });  
  28.           
  29.         Thread tsub = new Thread(new Runnable() {  
  30.               
  31.             @Override  
  32.             public void run() {  
  33.                 // TODO Auto-generated method stub  
  34.                 while(true){  
  35.                     bank.subMoney(100);  
  36.                     bank.lookMoney();  
  37.                     System.out.println("\n");  
  38.                     try {  
  39.                         Thread.sleep(1000);  
  40.                     } catch (InterruptedException e) {  
  41.                         // TODO Auto-generated catch block  
  42.                         e.printStackTrace();  
  43.                     }     
  44.                 }  
  45.             }  
  46.         });  
  47.         tsub.start();  
  48.           
  49.         tadd.start();  
  50.     }  
  51.       
  52.       
  53.   
  54. }  

代码很简单,我就不解释了,看看运行结果怎样呢?截取了其中的一部分,是不是很乱,有写看不懂。

[java] view plain copy
  1. 余额不足  
  2. 账户余额:0  
  3.   
  4.   
  5. 余额不足  
  6. 账户余额:100  
  7.   
  8.   
  9. 1441790503354存进:100  
  10. 账户余额:100  
  11.   
  12.   
  13. 1441790504354存进:100  
  14. 账户余额:100  
  15.   
  16.   
  17. 1441790504354取出:100  
  18. 账户余额:100  
  19.   
  20.   
  21. 1441790505355存进:100  
  22. 账户余额:100  
  23.   
  24.   
  25. 1441790505355取出:100  
  26. 账户余额:100  

四、使用同步时的代码

(1)同步方法:

即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

修改后的Bank.java

[java] view plain copy
  1. package threadTest;  
  2.   
  3. /** 
  4.  * @author ww 
  5.  * 
  6.  */  
  7. public class Bank {  
  8.   
  9.     private int count =0;//账户余额  
  10.       
  11.     //存钱  
  12.     public  synchronized void addMoney(int money){  
  13.         count +=money;  
  14.         System.out.println(System.currentTimeMillis()+"存进:"+money);  
  15.     }  
  16.       
  17.     //取钱  
  18.     public  synchronized void subMoney(int money){  
  19.         if(count-money < 0){  
  20.             System.out.println("余额不足");  
  21.             return;  
  22.         }  
  23.         count -=money;  
  24.         System.out.println(+System.currentTimeMillis()+"取出:"+money);  
  25.     }  
  26.       
  27.     //查询  
  28.     public void lookMoney(){  
  29.         System.out.println("账户余额:"+count);  
  30.     }  
  31. }  
再看看运行结果:

[html] view plain copy
  1. 余额不足  
  2. 账户余额:0  
  3.   
  4.   
  5. 余额不足  
  6. 账户余额:0  
  7.   
  8.   
  9. 1441790837380存进:100  
  10. 账户余额:100  
  11.   
  12.   
  13. 1441790838380取出:100  
  14. 账户余额:0  
  15. 1441790838380存进:100  
  16. 账户余额:100  
  17.   
  18.   
  19.   
  20.   
  21. 1441790839381取出:100  
  22. 账户余额:0  
瞬间感觉可以理解了吧。

注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

(2)同步代码块

即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

Bank.java代码如下:

[java] view plain copy
  1. package threadTest;  
  2.   
  3. /** 
  4.  * @author ww 
  5.  * 
  6.  */  
  7. public class Bank {  
  8.   
  9.     private int count =0;//账户余额  
  10.       
  11.     //存钱  
  12.     public   void addMoney(int money){  
  13.           
  14.         synchronized (this) {  
  15.             count +=money;  
  16.         }  
  17.         System.out.println(System.currentTimeMillis()+"存进:"+money);  
  18.     }  
  19.       
  20.     //取钱  
  21.     public   void subMoney(int money){  
  22.           
  23.         synchronized (this) {  
  24.             if(count-money < 0){  
  25.                 System.out.println("余额不足");  
  26.                 return;  
  27.             }  
  28.             count -=money;  
  29.         }  
  30.         System.out.println(+System.currentTimeMillis()+"取出:"+money);  
  31.     }  
  32.       
  33.     //查询  
  34.     public void lookMoney(){  
  35.         System.out.println("账户余额:"+count);  
  36.     }  
  37. }  

运行结果如下:

[html] view plain copy
  1. 余额不足  
  2. 账户余额:0  
  3.   
  4.   
  5. 1441791806699存进:100  
  6. 账户余额:100  
  7.   
  8.   
  9. 1441791806700取出:100  
  10. 账户余额:0  
  11.   
  12.   
  13. 1441791807699存进:100  
  14. 账户余额:100  

效果和方法一差不多。

注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

(3)使用特殊域变量(volatile)实现线程同步

    a.volatile关键字为域变量的访问提供了一种免锁机制 
    b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新 
    c.因此每次使用该域就要重新计算,而不是使用寄存器中的值 
    d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

Bank.java代码如下:
[java] view plain copy
  1. package threadTest;  
  2.   
  3. /** 
  4.  * @author ww 
  5.  * 
  6.  */  
  7. public class Bank {  
  8.   
  9.     private volatile int count = 0;// 账户余额  
  10.   
  11.     // 存钱  
  12.     public void addMoney(int money) {  
  13.   
  14.         count += money;  
  15.         System.out.println(System.currentTimeMillis() + "存进:" + money);  
  16.     }  
  17.   
  18.     // 取钱  
  19.     public void subMoney(int money) {  
  20.   
  21.         if (count - money < 0) {  
  22.             System.out.println("余额不足");  
  23.             return;  
  24.         }  
  25.         count -= money;  
  26.         System.out.println(+System.currentTimeMillis() + "取出:" + money);  
  27.     }  
  28.   
  29.     // 查询  
  30.     public void lookMoney() {  
  31.         System.out.println("账户余额:" + count);  
  32.     }  
  33. }  
运行效果怎样呢?
[html] view plain copy
  1. 余额不足  
  2. 账户余额:0  
  3.   
  4.   
  5. 余额不足  
  6. 账户余额:100  
  7.   
  8.   
  9. 1441792010959存进:100  
  10. 账户余额:100  
  11.   
  12.   
  13. 1441792011960取出:100  
  14. 账户余额:0  
  15.   
  16.   
  17. 1441792011961存进:100  
  18. 账户余额:100  

是不是又看不懂了,又乱了。这是为什么呢?就是因为volatile不能保证原子操作导致的,因此volatile不能代替synchronized。此外volatile会组织编译器对代码优化,因此能不使用它就不适用它吧。它的原理是每次要线程要访问volatile修饰的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。

(4)使用重入锁实现线程同步

    在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
     ReenreantLock类的常用方法有:
         ReentrantLock() : 创建一个ReentrantLock实例 
         lock() : 获得锁 
         unlock() : 释放锁 
    注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 
Bank.java代码修改如下:

[java] view plain copy
  1. package threadTest;  
  2.   
  3. import java.util.concurrent.locks.Lock;  
  4. import java.util.concurrent.locks.ReentrantLock;  
  5.   
  6. /** 
  7.  * @author ww 
  8.  * 
  9.  */  
  10. public class Bank {  
  11.   
  12.     private  int count = 0;// 账户余额  
  13.       
  14.     //需要声明这个锁  
  15.     private Lock lock = new ReentrantLock();  
  16.   
  17.     // 存钱  
  18.     public void addMoney(int money) {  
  19.         lock.lock();//上锁  
  20.         try{  
  21.         count += money;  
  22.         System.out.println(System.currentTimeMillis() + "存进:" + money);  
  23.           
  24.         }finally{  
  25.             lock.unlock();//解锁  
  26.         }  
  27.     }  
  28.   
  29.     // 取钱  
  30.     public void subMoney(int money) {  
  31.         lock.lock();  
  32.         try{  
  33.               
  34.         if (count - money < 0) {  
  35.             System.out.println("余额不足");  
  36.             return;  
  37.         }  
  38.         count -= money;  
  39.         System.out.println(+System.currentTimeMillis() + "取出:" + money);  
  40.         }finally{  
  41.             lock.unlock();  
  42.         }  
  43.     }  
  44.   
  45.     // 查询  
  46.     public void lookMoney() {  
  47.         System.out.println("账户余额:" + count);  
  48.     }  
  49. }  
运行效果怎么样呢?
[html] view plain copy
  1. 余额不足  
  2. 账户余额:0  
  3.   
  4.   
  5. 余额不足  
  6. 账户余额:0  
  7.   
  8.   
  9. 1441792891934存进:100  
  10. 账户余额:100  
  11.   
  12.   
  13. 1441792892935存进:100  
  14. 账户余额:200  
  15.   
  16.   
  17. 1441792892954取出:100  
  18. 账户余额:100  
效果和前两种方法差不多。

如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 。如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 

(5)使用局部变量实现线程同步

Bank.java代码如下:

[java] view plain copy
  1. package threadTest;  
  2.   
  3.   
  4. /** 
  5.  * @author ww 
  6.  * 
  7.  */  
  8. public class Bank {  
  9.   
  10.     private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){  
  11.   
  12.         @Override  
  13.         protected Integer initialValue() {  
  14.             // TODO Auto-generated method stub  
  15.             return 0;  
  16.         }  
  17.           
  18.     };  
  19.       
  20.   
  21.     // 存钱  
  22.     public void addMoney(int money) {  
  23.         count.set(count.get()+money);  
  24.         System.out.println(System.currentTimeMillis() + "存进:" + money);  
  25.           
  26.     }  
  27.   
  28.     // 取钱  
  29.     public void subMoney(int money) {  
  30.         if (count.get() - money < 0) {  
  31.             System.out.println("余额不足");  
  32.             return;  
  33.         }  
  34.         count.set(count.get()- money);  
  35.         System.out.println(+System.currentTimeMillis() + "取出:" + money);  
  36.     }  
  37.   
  38.     // 查询  
  39.     public void lookMoney() {  
  40.         System.out.println("账户余额:" + count.get());  
  41.     }  
  42. }  

运行效果:
[html] view plain copy
  1. 余额不足  
  2. 账户余额:0  
  3.   
  4.   
  5. 余额不足  
  6. 账户余额:0  
  7.   
  8.   
  9. 1441794247939存进:100  
  10. 账户余额:100  
  11.   
  12.   
  13. 余额不足  
  14. 1441794248940存进:100  
  15. 账户余额:0  
  16.   
  17.   
  18. 账户余额:200  
  19.   
  20.   
  21. 余额不足  
  22. 账户余额:0  
  23.   
  24.   
  25. 1441794249941存进:100  
  26. 账户余额:300  

看了运行效果,一开始一头雾水,怎么只让存,不让取啊?看看ThreadLocal的原理:

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,知识名字相同而已。所以就会发生上面的效果。

ThreadLocal与同步机制 
a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题

b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 穿瑜伽裤下边有缝怎么办 3岁宝宝比较内向怎么办 我想和我同学搞基怎么办 自己做的葡萄酒太甜怎么办 吉米学校想退款怎么办 汽车租赁公司不退押金怎么办 身材不好怎么办健身教练形象照 反祈祷式做不到怎么办 窦性心跳过缓怎么办 四维彩超查出胎儿心脏有问题怎么办 减脂肚子饿了怎么办 做瑜伽腰扭到了怎么办 出了汗怎么办活动反思 农村都是老人和孩子怎么办 瑜伽垫容易出现痕迹怎么办 37岁失业了该怎么办 45岁找什么工作怎么办 华为手机4g网速慢怎么办 解析软件包时出现问题怎么办 一字马不能下去髋摆不正怎么办 练轮瑜伽骆驼式腰疼怎么办 感昌咳嗽老不好怎么办 我感昌一直不好怎么办 腰间盘突出晚上睡觉痛怎么办 天天吃撑了怎么办啊 一吸气就想咳嗽怎么办 鼻子堵住了怎么办没法吸气时 一只鼻子不通气怎么办 练瑜伽后特别饿怎么办 站一天小腿肿了怎么办 练腹肌腰粗了怎么办 大专不交学费.然后退学怎么办 练瑜伽压腿一字马受伤了怎么办 银行工作人员借钱不还怎么办 借钱不还跑了但有工作怎么办 亲戚家借钱不还怎么办 学习瑜伽教练口令好复杂怎么办 练瑜伽腿的柔韧性不够怎么办 瑜伽扭转时手抓不到脚怎么办 练瑜伽腿部太硬怎么办 褶皱衣服不紧了怎么办