在多线程环境中,每个线程执行的时间是随机分配的,所以有的时候就会造成一个线程对一个对象操作到一半就让其他线程来操作了,结果导致了一些错误。
线程同步就是用来解决这种问题的,让我么来看看没有线程同步会有什么现象:
假如有2个人,男人和女人在不同的地方取同一个账户的钱,正巧是统一时刻,如果没有线程同步会有什么现象呢?
账户类:
/** * 账户类 * @author orangleliu */
public class Account {
private String accountNo; //账户号
private double balance; // 账户中的存款
//有参数和无参数的构造器
public Account(){
}
public Account(String accountNo,double balance){
this.accountNo=accountNo;
this.balance=balance;
}
//两个属性的bean方法
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//重写哈希和equels方法
@Override
public int hashCode() {
return this.accountNo.hashCode();
}
private boolean equels(Object obj) {
if(obj!=null&&obj.getClass()==Account.class){ //对象不为空,并且对象是Account的一个实例
Account ac=(Account)obj;
return ac.getAccountNo().equals(this.accountNo);
}
return false;
}
}
取钱类:
/** * 提取存款的主类 * @author lzz * */
class DrawThread extends Thread{
private Account account; //取钱操作账户
private double drawNum; //所取钱的数目
public DrawThread(){
}
public DrawThread (String name,Account account,double drawNum){
super(name);
this.account=account;
this.drawNum=drawNum;
}
@Override
public void run() {
// 对一个属性的同步锁
// synchronized (account) { //@1
if(account.getBalance()>=drawNum){
System.out.println(this.getName()+"成功取出"+this.drawNum);
account.setBalance(account.getBalance()-this.drawNum);
System.out.println("余额为"+account.getBalance());
}else{
System.out.println("对不起"+this.getName()+":您的余额不足");
}
// } //@1 } } 主要的测试类:
public class Maint{
public static void main(String[] args){
Account ac=new Account("1234", 1000);
new DrawThread("男人",ac,800).start();
new DrawThread("女人",ac,800).start();
}
}
让我们来看看取钱的结果:
女人成功取出800.0
男人成功取出800.0
余额为200.0
余额为-600.0
咦,好像最后取成负数了,银行亏了600块,这家子要倒霉了。。。。
为什么会出现这种状况呢
因为对余额确认不是每次一个线程去操作,而是2个线程可以同时操作这个账户(并发进行),男人钱还没去完,银行这边还没把余额改成200,女人就已经把钱取到手了,
最后导致2个人先取了钱,银行扣款完成,那多危险啊!!要是100个人同时去款呢,岂不是要先取出100*800后,银行才扣款成功?
那我们现在给account加上同步代码块,也就是把@1的那段注释给去掉
DrawThread 的run方法变成
public void run() {
// 对一个属性的同步锁
synchronized (account) { //@1
if(account.getBalance()>=drawNum){
System.out.println(this.getName()+"成功取出"+this.drawNum);
account.setBalance(account.getBalance()-this.drawNum);
System.out.println("余额为"+account.getBalance());
}else{
System.out.println("对不起"+this.getName()+":您的余额不足");
}
} //@1
}
再来看看结果
男人成功取出800.0 余额为200.0 对不起女人:您的余额不足
这次倒是符合正常的理解了,男人去完之后只有200块钱了,当然不让女人再取了,反正男人取钱也是要给他老婆。。
这就是那段同步代码块
synchronized (account) {
……
}
的作用。
在同一段时间只让一个线程对account对象操作,等这个线程整个同步代码块操作完了,才让下一个线程操作。
就是个类似串行的东西,大家排成队,一个一个的去执行同步了的代码块。
同步只是可以修饰代码块,还可以对整个方法进行同步,不同的写法,但是所要达到的目的是相同的。
文章部分引用:
1 《java疯狂讲义》李刚