“java 并发编程(六)之synchronized”介绍了java控制“临界区”访问的第一种方法“synchronized”,上节讲到synchronized方式主要注意这么几点
1、临界区的概念;
2、临界区的重入;
3、synchronized修饰静态方法的特殊性,如果静态方法也修改了“临界区”的公共资源,那么要注意,可能会出现并发问题;
这里我举出了个synchronized的使用实例:
模拟一个银行从账户取出一定金额,而公司存入账户金额这么一个过程。
用到了四个类
账户:Account
package com.z;
import java.util.concurrent.TimeUnit;
public class Account {
private int account;
public void setAccount(int newAccount){
this.account = newAccount;
}
public int getAccount(){
return account;
}
public synchronized boolean addAccount(int newAccount){
int tmp = account;
try {
TimeUnit.MICROSECONDS.sleep(30);
} catch (InterruptedException e) {
System.err.println("账户添加失败:" + e);
return false;
}
tmp += newAccount;
account = tmp;
return true;
}
public synchronized boolean subAccount(int newAccount){
int tmp = account;
try{
TimeUnit.MICROSECONDS.sleep(30);
} catch(InterruptedException e){
System.err.println("账户扣除失败:" + e);
return false;
}
tmp -= newAccount;
account = tmp;
return true;
}
}
银行类:
package com.z;
public class Bank implements Runnable{
private final Account account;
public Bank(Account account){
this.account = account;
}
public void run(){
for(int i = 0; i < 10; i ++){
account.subAccount(1000);
}
}
}
package com.z;
public class Company implements Runnable{
private final Account account;
public Company(Account account){
this.account = account;
}
public void run(){
for(int i = 0; i < 10; i ++){
account.addAccount(1000);
}
}
}
测试类:
package com.z;
public class Test {
public static void main(String[] args){
Account account = new Account();
account.setAccount(1000);
Thread[] bankThread = new Thread[100];
Thread[] companyThread = new Thread[100];
for(int i = 0; i < 100; i ++){
bankThread[i] = new Thread(new Bank(account));
companyThread[i] = new Thread(new Company(account));
}
for(int i = 0; i < 100; i ++){
bankThread[i].start();
companyThread[i].start();
}
for(int i = 0; i < 100; i ++){
try {
bankThread[i].join();
companyThread[i].join();
} catch (InterruptedException e) {
System.err.println(e);
}
}
System.out.println("账户余额是:" + account.getAccount());
}
}
通过反复多次执行输出的结果是:
账户余额是:1000
跟我们的预想是符合的。
在这里要注意两点:
1、如果我们把Account类中的addAccount和subAccount的synchronized关键字去掉,就会看到输出我们预想到的千奇百怪的结果;
2、我们观察addAccount和subAccount方法会发现,我们刻意写成这样的形式:
<span style="white-space:pre"> </span>int tmp = account;
try {
TimeUnit.MICROSECONDS.sleep(30);
} catch (InterruptedException e) {
System.err.println("账户添加失败:" + e);
return false;
}
tmp += newAccount;
account = tmp;
return true;
和
<pre name="code" class="java"><span style="white-space:pre"> </span>int tmp = account;
try {
TimeUnit.MICROSECONDS.sleep(30);
} catch (InterruptedException e) {
System.err.println("账户添加失败:" + e);
return false;
}
tmp -= newAccount;
account = tmp;
return true;
我们知道java会把类转化为字节码的形式,这正是java灵活性的根源,因此即使是最简单的赋值语句a=b,我们在观察生成的字节码的时候也会发现
是转化为了多条指令语句,因此即使是简单的a=b也会产生并发问题。所以,在这里我们用了个临时变量tmp来作为中转来模拟这个过程,这样会更容易产生
预想的问题。