1、由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。
2、一、多线程引起的数据访问安全问题下面看一个经典的问题,银行取钱的问题:1)、你有一张银行卡,里面有5000块钱,然后你到取款机取款,取出3000,当正在取的时候,取款机已经查询到你有5000块钱,然后正准备减去300块钱的时候2)、你的老婆拿着那张银行卡对应的存折到银行取钱,也要取3000.然后银行的系统查询,存折账户里还有6000(因为上面钱还没扣),所以它也准备减去3000,3)、你的卡里面减去3000,5000-3000=2000,并且你老婆的存折也是5000-3000=2000。4)、结果,你们一共取了6000,但是卡里还剩下2000。下面看程序的模拟过程:1. packagecom.bjpowernode.test;2. 3. publicclassGetMoneyTest{4. publicstaticvoidmain(String[]args){5. Accountaccount=newAccount(5000);6. GetMoneyRunrunnable=newGetMoneyRun(account);7. newThread(runnable,"你").start();8. newThread(runnable,"你老婆").start();9. }10. }11. 12. //账户Mode13. classAccount{14. privateintmoney;15. 16. publicAccount(intmoney){17. super();18. this.money=money;19. }20. 21. publicintgetMoney(){22. returnmoney;23. }24. 25. publicvoidsetMoney(intmoney){26. this.money=money;27. }28. 29. }30. //runnable类31. classGetMoneyRunimplementsRunnable{32. privateAccountaccount;33. 34. publicGetMoneyRun(Accountaccount){35. this.account=account;36. }37. 38. @Override39. publicvoidrun(){40. if(account.getMoney()>3000){41. System.out.println(Thread.currentThread().getName()+"的账户有"42. +account.getMoney()+"元");43. try{44. Thread.sleep(10);45. }catch(InterruptedExceptione){46. e.printStackTrace();47. }48. intlasetMoney=account.getMoney()-3000;49. account.setMoney(lasetMoney);50. System.out.println(Thread.currentThread().getName()+"取出来了3000元"51. +Thread.currentThread().getName()+"的账户还有"52. +account.getMoney()+"元");53. 54. }else{55. System.out.println("余额不足3000"+Thread.currentThread().getName()56. +"的账户只有"+account.getMoney()+"元");57. }58. 59. }60. 61. }
3、多次运行程序,可以看到有多种不同的结果,下面是其中的三种:1. 你的账户有5000元2. 你老婆的账户有5000元3. 你老婆取出来了3000元你老婆的账户还有2000元4. 你取出来了3000元你的账户还有-1000元1. 你的账户有5000元2. 你老婆的账户有5000元3. 你老婆取出来了3000元你老婆的账户还有-1000元4. 你取出来了3000元你的账户还有-1000元1. 你的账户有5000元2. 你老婆的账户有5000元3. 你老婆取出来了3000元你老婆的账户还有2000元4. 你取出来了3000元你的账户还有2000元 可以看到,由于有两个线程同时访问这个account对象,导致取钱发生的账户发生问题。当多个线程访问同一个数据的时候,非常容易引发问题。为了避免这样的事情发生,我们要保证线程同步互斥,所谓同步互斥就是:并发执行的多个线程在某一时间内只允许一个线程在执行以访问共享数据。
4、同步互斥锁同步锁的原理:Java中每个对象都有一个内置同步锁。Java中可以使用synchronized关键字来取得一个对象的同步锁。synchronized的使用方式,是在一段代码块中,加上synchronized(object){ ... }例如,有一个show方法,里面有synchronized的代码段:1. publicvoidshow(){2. synchronized(object){3. ......4. }5. }这其中的object可以使任何对象,表示当前线程取得该对象的锁。一个对象只有一个锁,所以其他任何线程都不能访问该对象的所有由synchronized包括的代码段,直到该线程释放掉这个对象的同步锁(释放锁是指持锁线程退出了synchronized同步方法或代码块)。注意:synchronized使用方式有几个要注意的地方(还是以上面的show方法举例):①、取得同步锁的对象为this,即当前类对象,这是使用的最多的一种方式1. publicvoidshow(){2. synchronized(this){3. ......4. }5. }
5、将synchronized加到方法上,这叫做同步方法,相当于第一种方式的缩写1. publicsynchronizedvoidshow(){2. 3. } 静态方法的同步1. publicstaticsynchronizedvoidshow(){2. 3. } 相当于1. publicstaticvoidshow(){2. synchronized(当前类名.class)3. } 相当于取得类对象的同步锁,注意它和取得一个对象的同步锁不一样
6、明白了同步锁的原理和synchronized关键字的使用,那么解决上面的取钱问题就很简单了,我们只要对run方法里面加上synchro荏鱿胫协nized关键字就没有问题了,如下:1. @Override2. publicvoidrun(){3. synchronized(account){4. if(account.getMoney()>3000){5. System.out.println(Thread.currentThread().getName()+"的账户有"6. +account.getMoney()+"元");7. try{8. Thread.sleep(10);9. }catch(InterruptedExceptione){10. e.printStackTrace();11. }12. intlasetMoney=account.getMoney()-3000;13. account.setMoney(lasetMoney);14. System.out.println(Thread.currentThread().getName()15. +"取出来了3000元"+Thread.currentThread().getName()16. +"的账户还有"+account.getMoney()+"元");17. 18. }else{19. System.out.println("余额不足3000"20. +Thread.currentThread().getName()+"的账户只有"21. +account.getMoney()+"元");22. }23. 24. }25. }当甲线程执行run方法的时候,它使用synchronized (account)取得了account对象的同步锁,那么只要它没释放掉这个锁,那么当乙线程执行到run方法的时候,它就不能获得继续执行的锁,所以只能等甲线程执行完,然后释放掉锁,乙线程才能继续执行。