在多线程环境中,可能会出现两个甚至更多的线程试图同时访问同一个资源。必须对这种潜在的资源冲突进行预防。
在线程使用一个资源时为其加入锁机制。访问资源的第一个线程对其加上锁之后,其他线程便不能再使用那个资源,除非被解锁。
比如银行取钱的例子:
首先有一个银行账户:
/**
* 银行账户类,里面的余额为1000
*/
class Bank {
private int balance = 1000;
/**
* 取钱的方法
* @param number 取钱的金额
* @return
*/
public int getMoney(int number) {
if (number < 0) {
return -1;
} else if (number > balance) {
return -2;
} else if (balance < 0) {
return -3;
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= number;
System.out.println("balance is:" + balance);
return number;
}
}
}
取钱,可以可以通过柜台取钱,可以通过ATM取钱
/**
* ATM取钱
*/
class AtmFetchMoney extends Thread {
private Bank bank;
public AtmFetchMoney(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
System.out.println("ATM取钱:" + bank.getMoney(800));
}
}
/**
* 柜台取钱
*/
class GuiTaiFetchMoney extends Thread {
private Bank bank;
public GuiTaiFetchMoney(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
System.out.println("柜台取钱:" + bank.getMoney(800));
}
}
现在开始模拟ATM和柜台同时在一起账户进行取钱的过程:
public class FetchMoney {
public static void main(String[] args) {
/**
* 模拟取钱过程,两个线程同时对一个账户进行操作。
*/
Bank bank = new Bank();
Thread t1 = new AtmFetchMoney(bank);
Thread t2 = new GuiTaiFetchMoney(bank);
t1.start();
t2.start();
}
}
结果1:
balance is:-600
balance is:-600
柜台取钱:800
ATM取钱:800
-600 -600 第一个线程进来之后减去800之后,没有执行输出余额,那么第二个也减去了800,所以最后分别输出就是-600 -600
结果2:
balance is:200
balance is:200
ATM取钱:800
柜台取钱:800
200 200第一个线程进来之后800之后,没有减去余额,那么第二个线程进来之后拿到余额1000-800=200,所以最后分别输出就是200 200
结果3:
balance is:200
ATM取钱:800
balance is:-600
柜台取钱:800
200 -600第一个线程进来之后没有减去800,输出余额200,那么第二个线程进来之后拿到余额200-800=-600,最后输出的是200 -600
通过上面的例子发现,多线程中如果对同一个资源进行操作会出现资源竞争的情况,该如何解决呢?
由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问,利用synchronized关键字。
在方法前面加上synchronized关键字
public synchronized int getMoney(int number) {
//对方法加上synchronized关键字
if (number < 0) {
return -1;
} else if (number > balance) {
return -2;
} else if (balance < 0) {
return -3;
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= number;
System.out.println("balance is:" + balance);
return number;
}
}
输出结果:
balance is:200
ATM取钱:800
柜台取钱:-2
可以看到ATM取钱之后,余额只剩下200,柜台再去取钱的时候就返回-2,表示余额不足,不会出现了余额不正确的情况。
1.synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果 再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
2.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
3.每个对象只有一个锁(lock)与之相关联。
4.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
1。某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线 程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的 synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2。某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。