首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

线程保险总结

2012-12-20 
线程安全总结最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣。已经拟好了提

线程安全总结

最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣。已经拟好了提纲,大概分为这几个主题:Java代码

  1. for(int?i=0;i<10;i++)??
  2. ?a++;??

?


线程有可能只对工作内存中的副本进行赋值,只到最后一次赋值后才同步到主存储区,所以assign,store,weite顺序可以由JVM实现系统决定。假设有一个共享变量x,线程a执行x=x+1。从上面的描述中可以知道x=x+1并不是一个原子操作,它的执行过程如下:
1 从主存中读取变量x副本到工作内存
2 给x加1
3 将x加1后的值写回主Java代码

  1. public?class?Account?{??
  2. ??
  3. ????private?int?balance;??
  4. ??
  5. ????public?Account(int?balance)?{??
  6. ????????this.balance?=?balance;??
  7. ????}??
  8. ??
  9. ????public?int?getBalance()?{??
  10. ????????return?balance;??
  11. ????}??
  12. ??
  13. ????public?void?add(int?num)?{??
  14. ????????balance?=?balance?+?num;??
  15. ????}??
  16. ??
  17. ????public?void?withdraw(int?num)?{??
  18. ????????balance?=?balance?-?num;??
  19. ????}??
  20. ??
  21. ????public?static?void?main(String[]?args)?throws?InterruptedException?{??
  22. ????????Account?account?=?new?Account(1000);??
  23. ????????Thread?a?=?new?Thread(new?AddThread(account,?20),?"add");??
  24. ????????Thread?b?=?new?Thread(new?WithdrawThread(account,?20),?"withdraw");??
  25. ????????a.start();??
  26. ????????b.start();??
  27. ????????a.join();??
  28. ????????b.join();??
  29. ????????System.out.println(account.getBalance());??
  30. ????}??
  31. ??
  32. ????static?class?AddThread?implements?Runnable?{??
  33. ????????Account?account;??
  34. ????????int?????amount;??
  35. ??
  36. ????????public?AddThread(Account?account,?int?amount)?{??
  37. ????????????this.account?=?account;??
  38. ????????????this.amount?=?amount;??
  39. ????????}??
  40. ??
  41. ????????public?void?run()?{??
  42. ????????????for?(int?i?=?0;?i?<?200000;?i++)?{??
  43. ????????????????account.add(amount);??
  44. ????????????}??
  45. ????????}??
  46. ????}??
  47. ??
  48. ????static?class?WithdrawThread?implements?Runnable?{??
  49. ????????Account?account;??
  50. ????????int?????amount;??
  51. ??
  52. ????????public?WithdrawThread(Account?account,?int?amount)?{??
  53. ????????????this.account?=?account;??
  54. ????????????this.amount?=?amount;??
  55. ????????}??
  56. ??
  57. ????????public?void?run()?{??
  58. ????????????for?(int?i?=?0;?i?<?100000;?i++)?{??
  59. ????????????????account.withdraw(amount);??
  60. ????????????}??
  61. ????????}??
  62. ????}??
  63. }??

?


第一次执行结果为10200,第二次执行结果为1060,每次执行的结果都是不确定的,因为线程的执行顺序是不可预见的。这是java同步产生的根源,synchronized关键字保证了多个线程对于同步块是互斥的,synchronized作为一种同步手段,解决java多线程的执行有序性和内存可见性,而volatile关键字之解决多线程的内存可见性问题。后面将会详细介绍。



synchronized关键字Java代码

  1. synchronized(锁){??
  2. ?????临界区代码??
  3. }???

?


为了保证银行账户的安全,可以操作账户的方法如下:

Java代码
  1. public?synchronized?void?add(int?num)?{??
  2. ?????balance?=?balance?+?num;??
  3. }??
  4. public?synchronized?void?withdraw(int?num)?{??
  5. ?????balance?=?balance?-?num;??
  6. }??

?


刚才不是说了synchronized的用法是这样的吗:

Java代码
  1. synchronized(锁){??
  2. 临界区代码??
  3. }??

?


那么对于public synchronized void add(int num)这种情况,意味着什么呢?其实这种情况,锁就是这个方法所在的对象。同理,如果方法是public? static synchronized void add(int num),那么锁就是这个方法所在的class。
??????? 理论上,每个对象都可以做为锁,但一个对象做为锁时,应该被多个线程共享,这样才显得有意义,在并发环境下,一个没有共享的对象作为锁是没有意义的。假如有这样的代码:

Java代码
  1. public?class?ThreadTest{??
  2. ??public?void?test(){??
  3. ?????Object?lock=new?Object();??
  4. ?????synchronized?(lock){??
  5. ????????//do?something??
  6. ?????}??
  7. ??}??
  8. }??

?


lock变量作为一个锁存在根本没有意义,因为它根本不是共享对象,每个线程进来都会执行Object lock=new Object();每个线程都有自己的lock,根本不存在锁竞争。
??????? 每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,当一个被线程被唤醒(notify)后,才会进入到就绪队列,等待cpu的调度。当一开始线程a第一次执行account.add方法时,jvm会检查锁对象account的就绪队列是否已经有线程在等待,如果有则表明account的锁已经被占用了,由于是第一次运行,account的就绪队列为空,所以线程a获得了锁,执行account.add方法。如果恰好在这个时候,线程b要执行account.withdraw方法,因为线程a已经获得了锁还没有释放,所以线程b要进入account的就绪队列,等到得到锁后才可以执行。
一个线程执行临界区代码过程如下:
1 获得同步锁
2 清空工作内存
3 从主存拷贝变量副本到工作内存
4 对这些变量计算
5 将变量从工作内存写回到主存
6 释放锁
可见,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。


生产者/消费者模式Java代码

  1. Object?lock=new?Object();//声明了一个对象作为锁??
  2. ???synchronized?(lock)?{??
  3. ???????balance?=?balance?-?num;??
  4. ???????//这里放弃了同步锁,好不容易得到,又放弃了??
  5. ???????lock.wait();??
  6. }??

?


如果一个线程获得了锁lock,进入了同步块,执行lock.wait(),那么这个线程会进入到lock的阻塞队列。如果调用lock.notify()则会通知阻塞队列的某个线程进入就绪队列。
声明一个盘子,只能放一个鸡蛋

Java代码
  1. package?com.jameswxx.synctest;??
  2. public?class?Plate{??
  3. ??List<Object>?eggs=new?ArrayList<Object>();??
  4. ??public?synchronized??Object?getEgg(){??
  5. ?????if(eggs.size()==0){??
  6. ????????try{??
  7. ????????????wait();??
  8. ????????}catch(InterruptedException?e){??
  9. ????????}??
  10. ?????}??
  11. ??
  12. ????Object?egg=eggs.get(0);??
  13. ????eggs.clear();//清空盘子??
  14. ????notify();//唤醒阻塞队列的某线程到就绪队列??
  15. ????return?egg;??
  16. }??
  17. ??
  18. ?public?synchronized??void?putEgg(Object?egg){??
  19. ????If(eggs.size()>0){??
  20. ??????try{??
  21. ?????????wait();??
  22. ??????}catch(InterruptedException?e){??
  23. ??????}??
  24. ????}??
  25. ????eggs.add(egg);//往盘子里放鸡蛋??
  26. ????notify();//唤醒阻塞队列的某线程到就绪队列??
  27. ??}??
  28. }??

?

font-size: s

热点排行