为什么需要同步机制?
举个例子,如果没有同步机制,你的支付宝绑定了一张银行卡,里面有1000,你拿着这张卡去银行存钱,准备存5000,点击存钱的同时用支付宝转账500给朋友,如果转账和存钱同时发生,那么他们操作的对象是同一个money,此时money=1000,这就会出现一种情况,转账后money=1000-500=500,存钱后money=1000+5000=6000;那最后的余额会变为6000,但其实应该是5500,这时银行就亏了500了。
可见上面存取money是存在一个临界区,这个临界区在同一时间内只允许一个线程执行时才能避免出现上述的情况,而同步就是为了解决这种情况
什么是同步?
java的同步机制:当一个线程试图访问一个临界区时,它将使用一种同步机制来查是不是已有其他线程进入临界区。如果没有,它就进入,如果有,它就会等待,直到临界区为空。
实例
下面是一个公司和银行同时操作账户的实例
package kshon.synchronize;
/**
* 账户类
*/
public class Account {
private int money;
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
//存钱(目前没有加同步synchronized)
public void add(int m){
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
money += m;
System.out.println("成功存钱:"+m+"——"+money);
}
//取钱(目前没有加同步synchronized)try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
money -= m;
System.out.println("成功取钱:"+m+"——剩余"+money);
}
}
package kshon.synchronize;
/**
* 银行取钱
*/
public class Bank implements Runnable{
private Account account;
public Bank(Account acc){
account = acc;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<10;i++){
account.sub(i*10);
}
}
}
package kshon.synchronize;
/**
* 公司
*/
public class Company implements Runnable{
private Account account;
public Company(Account acc){
account = acc;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<10;i++){
account.add(i*20);
}
}
}
package kshon.synchronize;
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Account acc = new Account();
acc.setMoney(1000);
Company company = new Company(acc);
Bank bank = new Bank(acc);
Thread t1 = new Thread(company);
Thread t2 = new Thread(bank);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果如下:
由于cpu的调度,每次运行的结果都可能是不同的,但是你们看第三条,余额为1000的,取10块钱之后变成1010,反而多了10块,这是因为后面存取20块,他们发生了错乱。这就是没有使用线程同步的后果,很明显这是非常致命的,对于我们编程人员来说绝不能犯这种小错误。
接下来我们使用同步synchronized,修改刚才的账户类Account
package kshon.synchronize;
/**
* 账户类
*/
public class Account {
private int money;
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
//存钱,使用同步
public synchronized void add(int m){
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
money += m;
System.out.println("成功存钱:"+m+"——"+money);
}
//取钱,使用同步
public synchronized void sub(int m){
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
money -= m;
System.out.println("成功取钱:"+m+"——剩余"+money);
}
}
然后运行Test类:
可以看到,这次的数据就对了,因为使用了同步,存钱和取钱在同一时间内只能执行一个。每次运行结果可能有所不同,但是其最终的余额总是1450.