在Java中我们可以使用synchronized关键字来控制一段代码的并发访问。使用synchronized关键字修饰的代码在同一时间只有一个线程可以访问,其他要访问这个代码块的线程将被挂起。
使用synchronized关键字可以控制并发访问同时修改一份数据造成数据不一致的问题,但是synchronized会降低系统性能,因为即使你启动再多的线程,同一时间还是只有一个线程能访问,其他线程都在挂起,所以使用了synchronized也就没有真正意义上的并发了。
我们可以使用synchronized关键字修饰方法,那么这个方法就是同步方法。我们也可以使用synchronized来修饰代码款,那么这个代码块就是同步代码块。
示例代码
下面我们看一个经典的银行账号的例子,在这个例子中我们模拟一个银行账号的存取款过程,同时开启多个线程模拟多次并发的存取款操作,最后我们来看下账户余额。
创建Account类来模拟银行账户,其中修改账户金额的方法使用synchronized关键字修饰,打印账户变动并模拟每次账户修改操作需要1秒钟完成。
public class Account {
private int amount;
public Account(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
//public int modify(int amount) {
public synchronized int modify(int amount) {
if (amount > 0) {
System.out.printf("%s: 向账户存款%d元.\n", Thread.currentThread().getName(), amount);
} else if (amount < 0 && this.amount >= -amount) {
System.out.printf("%s: 余额充足,从账户取款%d元.\n", Thread.currentThread().getName(), amount);
} else if (amount < 0 && this.amount < -amount) {
System.out.printf("%s: 余额不足,取款失败.\n", Thread.currentThread().getName());
return this.amount;
} else {
return this.amount;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.amount = this.amount + amount;
return amount;
}
}
新建线程类模拟用户的存取款操作
public class MyRunnable implements Runnable{
private Account account;
private int amount;
public MyRunnable(Account account, int amount) {
this.account = account;
this.amount = amount;
}
@Override
public void run() {
account.modify(amount);
}
}
最后再主方法类中,新建一个余额为1000元的账户,然后使用4个线程分别模拟1次存款操作,3次取款操作,正常情况应该是有一次取款失败,因为最后账户余额已经不够1000元了。
public class Main {
public static void main(String[] args) {
Account account = new Account(1000);
Thread[] threads = new Thread[4];
threads[0] = new Thread(new MyRunnable(account, 1000));
threads[1] = new Thread(new MyRunnable(account, -1000));
threads[2] = new Thread(new MyRunnable(account, -1000));
threads[3] = new Thread(new MyRunnable(account, -1000));
try {
for (int i = 0; i < 4; i++) {
threads[i].start();
Thread.sleep(50);
}
for (int i = 0; i < 4; i++) {
threads[i].join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Main: 账户余额:%d\n", account.getAmount());
}
}
查看日志,我们发现有一次取款失败,并且最后账户余额为0.
Thread-0: 向账户存款1000元.
Thread-3: 余额充足,从账户取款-1000元.
Thread-2: 余额充足,从账户取款-1000元.
Thread-1: 余额不足,取款失败.
Main: 账户余额:0
我们如果去掉synchronized关键字修饰,你会发现三次取款都成功了,并且最后账户余额为-1000元。
Thread-0: 向账户存款1000元.
Thread-1: 余额充足,从账户取款-1000元.
Thread-2: 余额充足,从账户取款-1000元.
Thread-3: 余额充足,从账户取款-1000元.
Main: 账户余额:-1000