线程安全总结
最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣。已经拟好了提纲,大概分为这几个主题:Java代码
- for(int?i=0;i<10;i++)??
- ?a++;??
?
线程有可能只对工作内存中的副本进行赋值,只到最后一次赋值后才同步到主存储区,所以assign,store,weite顺序可以由JVM实现系统决定。假设有一个共享变量x,线程a执行x=x+1。从上面的描述中可以知道x=x+1并不是一个原子操作,它的执行过程如下:
1 从主存中读取变量x副本到工作内存
2 给x加1
3 将x加1后的值写回主Java代码
- public?class?Account?{??
- ??
- ????private?int?balance;??
- ??
- ????public?Account(int?balance)?{??
- ????????this.balance?=?balance;??
- ????}??
- ??
- ????public?int?getBalance()?{??
- ????????return?balance;??
- ????}??
- ??
- ????public?void?add(int?num)?{??
- ????????balance?=?balance?+?num;??
- ????}??
- ??
- ????public?void?withdraw(int?num)?{??
- ????????balance?=?balance?-?num;??
- ????}??
- ??
- ????public?static?void?main(String[]?args)?throws?InterruptedException?{??
- ????????Account?account?=?new?Account(1000);??
- ????????Thread?a?=?new?Thread(new?AddThread(account,?20),?"add");??
- ????????Thread?b?=?new?Thread(new?WithdrawThread(account,?20),?"withdraw");??
- ????????a.start();??
- ????????b.start();??
- ????????a.join();??
- ????????b.join();??
- ????????System.out.println(account.getBalance());??
- ????}??
- ??
- ????static?class?AddThread?implements?Runnable?{??
- ????????Account?account;??
- ????????int?????amount;??
- ??
- ????????public?AddThread(Account?account,?int?amount)?{??
- ????????????this.account?=?account;??
- ????????????this.amount?=?amount;??
- ????????}??
- ??
- ????????public?void?run()?{??
- ????????????for?(int?i?=?0;?i?<?200000;?i++)?{??
- ????????????????account.add(amount);??
- ????????????}??
- ????????}??
- ????}??
- ??
- ????static?class?WithdrawThread?implements?Runnable?{??
- ????????Account?account;??
- ????????int?????amount;??
- ??
- ????????public?WithdrawThread(Account?account,?int?amount)?{??
- ????????????this.account?=?account;??
- ????????????this.amount?=?amount;??
- ????????}??
- ??
- ????????public?void?run()?{??
- ????????????for?(int?i?=?0;?i?<?100000;?i++)?{??
- ????????????????account.withdraw(amount);??
- ????????????}??
- ????????}??
- ????}??
- }??
?
第一次执行结果为10200,第二次执行结果为1060,每次执行的结果都是不确定的,因为线程的执行顺序是不可预见的。这是java同步产生的根源,synchronized关键字保证了多个线程对于同步块是互斥的,synchronized作为一种同步手段,解决java多线程的执行有序性和内存可见性,而volatile关键字之解决多线程的内存可见性问题。后面将会详细介绍。
synchronized关键字Java代码
- synchronized(锁){??
- ?????临界区代码??
- }???
?
为了保证银行账户的安全,可以操作账户的方法如下:
- public?synchronized?void?add(int?num)?{??
- ?????balance?=?balance?+?num;??
- }??
- public?synchronized?void?withdraw(int?num)?{??
- ?????balance?=?balance?-?num;??
- }??
?
刚才不是说了synchronized的用法是这样的吗:
- synchronized(锁){??
- 临界区代码??
- }??
?
那么对于public synchronized void add(int num)这种情况,意味着什么呢?其实这种情况,锁就是这个方法所在的对象。同理,如果方法是public? static synchronized void add(int num),那么锁就是这个方法所在的class。
??????? 理论上,每个对象都可以做为锁,但一个对象做为锁时,应该被多个线程共享,这样才显得有意义,在并发环境下,一个没有共享的对象作为锁是没有意义的。假如有这样的代码:
- public?class?ThreadTest{??
- ??public?void?test(){??
- ?????Object?lock=new?Object();??
- ?????synchronized?(lock){??
- ????????//do?something??
- ?????}??
- ??}??
- }??
?
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代码
- Object?lock=new?Object();//声明了一个对象作为锁??
- ???synchronized?(lock)?{??
- ???????balance?=?balance?-?num;??
- ???????//这里放弃了同步锁,好不容易得到,又放弃了??
- ???????lock.wait();??
- }??
?
如果一个线程获得了锁lock,进入了同步块,执行lock.wait(),那么这个线程会进入到lock的阻塞队列。如果调用lock.notify()则会通知阻塞队列的某个线程进入就绪队列。
声明一个盘子,只能放一个鸡蛋
- package?com.jameswxx.synctest;??
- public?class?Plate{??
- ??List<Object>?eggs=new?ArrayList<Object>();??
- ??public?synchronized??Object?getEgg(){??
- ?????if(eggs.size()==0){??
- ????????try{??
- ????????????wait();??
- ????????}catch(InterruptedException?e){??
- ????????}??
- ?????}??
- ??
- ????Object?egg=eggs.get(0);??
- ????eggs.clear();//清空盘子??
- ????notify();//唤醒阻塞队列的某线程到就绪队列??
- ????return?egg;??
- }??
- ??
- ?public?synchronized??void?putEgg(Object?egg){??
- ????If(eggs.size()>0){??
- ??????try{??
- ?????????wait();??
- ??????}catch(InterruptedException?e){??
- ??????}??
- ????}??
- ????eggs.add(egg);//往盘子里放鸡蛋??
- ????notify();//唤醒阻塞队列的某线程到就绪队列??
- ??}??
- }??
?
font-size: s