由前面几篇文章可知:线程什么时候运行是不确定的,即:
●各个线程是通过竞争CPU的时间而获得运行机会的;
●各线程什么时候获得CPU时间,占用多久,是不可预测的;
●一个正在运行的线程在什么地方被暂停是不确定的;
●可能一个线程中一个方法(或一个代码块)刚执行到一半,这个线程就丧失了CPU的使用权,从而导致很多问题;
上面几条都透露着几个字:不确定。这个问题,在实际开发中也会带来不可预测的结果,问题;
……………………………………………………
如下面问题:银行账户的存款和取款。
(1)首先,存款和取款各是不同的线程在执行;
(2)存款操作和取款操作都是分很多步骤执行;
(3)不同的线程之间切换是随机的,即可能取款线程执行一半就停了,转去执行存款线程,而存款线程执行一般也可能暂停,转去执行取款线程;这种现象就会导致,最终的账户金额存在错误计算;
如下面代码:看个大概即可,程序很简单,主要是传达一种问题而已。
(1)Bank类,主要包括,存款方法和取款方法;(这儿用slee()方法,防止输出结果输出太快,看不清;而且实际中,一个方法的执行也需要一定的时间的);
public class Bank {
private String account;// 账号
private int balance;// 账户余额
public Bank(String account, int balance) {
this.account = account;
this.balance = balance;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Bank [账号:" + account + ", 余额:" + balance + "]";
}
// 存款
public void saveAccount() {
// 获取当前的账号余额
int balance = getBalance();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 修改余额,存100元
balance += 100;
// 修改账户余额
setBalance(balance);
// 输出存款后的账户余额
System.out.println("存款后的账户余额为:" + balance);
}
public void drawAccount() {
// 在不同的位置处添加sleep方法
// 获得当前的帐户余额
int balance = getBalance();
// 修改余额,取200
balance = balance - 200;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 修改帐户余额
setBalance(balance);
System.out.println("取款后的帐户余额:" + balance);
}
}
(2)取款类,做成了一个线程类(因为取款操作是一个单独的线程)
//取款
public class DrawAccount implements Runnable{
Bank bank;
public DrawAccount(Bank bank){
this.bank=bank;
}
@Override
public void run() {
bank.drawAccount();
}
}
(3)存款类,也做成了一个线程类
//存款
public class SaveAccount implements Runnable{
Bank bank;
public SaveAccount(Bank bank){
this.bank=bank;
}
public void run(){
bank.saveAccount();
}
}
(4)测试类
public class Test {
public static void main(String[] args) {
// 创建帐户,给定余额为1000
Bank bank=new Bank("1001",1000);
//创建线程对象
SaveAccount sa=new SaveAccount(bank);
DrawAccount da=new DrawAccount(bank);
Thread save=new Thread(sa);
Thread draw=new Thread(da);
save.start();
draw.start();
try {
draw.join();
save.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(bank);
}
}
解决办法:
●为了保证在存款或取款的时候,不允许其他线程对账户余额进行操作;需要将Bank对象进行锁定,即某个线程在操作Bank对象时,其他线程都不允许进行操作;
●使用关键字synchronized(同步关键字)实现;该关键字,可以确保共享对象,在同一个时刻只能被一个线程访问,这种机制成为线程同步,或线程互斥。
●synchroniced具体的使用技巧,需要在以后的实际开发中逐步加深理解,逐步总结其应用场景,惯用应用技巧等。
synchroniced:
如上面Bank类改造如下:分别在存款方法和取款方法处,使用了synchroniced关键字。
如:saveAccount()方法添加synchronized后表示:这个方法中的所有代码在执行完毕之前,其他线程是不能把他打断的。
public class Bank {
private String account;// 账号
private int balance;// 账户余额
public Bank(String account, int balance) {
this.account = account;
this.balance = balance;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Bank [账号:" + account + ", 余额:" + balance + "]";
}
// 存款
// saveAccount()方法添加synchronized后表示:这个方法中的所有代码在执行完毕之前,其他线程是不能把他打断的。
public synchronized void saveAccount() {
// 获取当前的账号余额
int balance = getBalance();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 修改余额,存100元
balance += 100;
// 修改账户余额
setBalance(balance);
// 输出存款后的账户余额
System.out.println("存款后的账户余额为:" + balance);
}
public void drawAccount() {
// 语句块添加synchronized后表示:这个方法中的所有代码在执行完毕之前,其他线程是不能把他打断的。
// 因为当前语句块是针对Bank类的,即是针对Bank类型的对象的,所以直接this就行了
synchronized (this) {
// 在不同的位置处添加sleep方法
// 获得当前的帐户余额
int balance = getBalance();
// 修改余额,取200
balance = balance - 200;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 修改帐户余额
setBalance(balance);
System.out.println("取款后的帐户余额:" + balance);
}
}
}