一、线程不安全
之前我们介绍了线程的应用,那么在线程的应用中,会有一些意想不到的问题出现,比如我们今天提到的,线程安全。
说到线程安全,首先我们要理解多线程到底是怎么样实现的:
在下图中,我们展示了三个有三条指令的线程的一种可能执行的顺序。虽然在线程内指令是依次执行的,但是在线程之间,指令执行顺序是随机的。
那么我们假设三个线程的三条指令都是一样的,且都有同一个对象a
//a.value的值初始化为9
if(a.value <10){ //Instruction1
a.value++; //Instruction2
System.out.println(a.value); //Instruction3
}
按照上图的顺序,记录一下每一步的结果
Thread2-Instruction1:a.value<10,判断可以执行if分支
Thread1-Instruction1:a.value<10,判断可以执行if分支
Thread1-Instruction2:a.value++,a.value此时为10
Thread3-Instruction1:a.value=10,判断不执行if分支
Thread2-Instruction2:a.value++,a.value此时为11
执行到这一步我们已经可以看到,a.value的值已经超出了我们希望它可所在的范围即小于等于10。a.value经过Thread1-Instruction2,已经成为了10,但是由于在Thread2-Instruction1执行判断的时候,已经通过了判断,所以可以执行Thread2-Instruction2,即便a.value现在已经不满足小于10的先决条件了,依然执行了Thread2-Instruction2。
这样我们就展示了上述代码不是线程安全的。那么为了达到线程安全的目的,我们希望特定的代码是连续执行的,比如让Instruction1和Instruction2,就不会出现上述的问题了,那么如何实现呢?
二、同步监听器(锁)
同步监听器,也就是锁。顾名思义,锁的作用就是将一组指令锁在一起,让这组指令在被执行的时候是连续执行的。这里我们要引入一个新的关键词synchronized,使用方法为
synchronized(Object){
//Instruction 1
//Instruction 2
//Instruction 3
//******
//Instruction n
}
通过这样的方式,我们可以使得Instruction1到n被连续的执行给不会切换到执行其他线程的指令。值得注意的是,Object是一个对象,所有线程都会使用的同一个对象,即不同的线程要用同一个锁,如果每个线程都有自己的锁,那就相当于没有锁喽。
三、模拟银行取款
我们模拟一下银行的取款,ATM机里一共有100块钱,a、b、c同时不停的每次取2块钱。
先要有钱哦。
public class Money {
public int money = 100;
}
然后还要有人
public class User extends Thread{
public Money money;
public User (Money money) {
this.money = money;
}
public void run() {
while (money.money>0) {
synchronized(money) {
if (money.money>0) {
money.money-=2;
System.out.println(this.getName()+"取钱后还剩下"+money.money);
}
}
}
}
}
最后让银行运行起来吧
public class Bank {
public static void main(String[] args) {
Bank b = new Bank();
b.draw();
}
public void draw() {
Money m = new Money();
User a = new User(m);
a.setName("a");
a.start();
User b = new User(m);
b.setName("b");
b.start();
User c = new User(m);
c.setName("c");
c.start();
}
}