线程安全问题
当多个线程同时操作堆区或者方法区的某个数据时,可能会出现数据不一致的现象,成为线程安全问题
以多人从同一个账户中取钱作为例子:
账户类
public class BankAccount {
int balance = 10000; //账户余额(万元)
//取钱操作,约定每次取钱1000万
public void withdraw() {
System.out.println(Thread.currentThread().getName() + "取钱前,查询余额:" + balance + "万元");
balance -= 1000;
System.out.println(Thread.currentThread().getName() + "取了1000万后余额:" + balance + "万元");
}
}
线程类
/*
*
* 创建线程类,模拟人从账户中取钱
*
* */
public class PersonThread extends Thread {
private BankAccount account; //银行账户
public PersonThread(BankAccount account) {
super();
this.account = account;
}
@Override
public void run() {
account.withdraw();
}
}
测试类
/*
*
* 使用线程模拟多人同时从某个账户中取钱
*
* */
public class Test {
public static void main(String[] args) {
//先开户
BankAccount account = new BankAccount();
//创建三个线程,模拟三个人取钱
PersonThread p1 = new PersonThread(account);
PersonThread p2 = new PersonThread(account);
PersonThread p3 = new PersonThread(account);
p1.setName("bingbing");
p2.setName("yuanyuan");
p3.setName("jianjian");
p1.start();
p2.start();
p3.start();
/*
*
* 运行当前程序,可能会出现数据不一致情况,这就是线程安全问题
*
* */
}
}
以上程序的运行结果会出现这样的情况
在查询的时候显示的都是一样的结果,但是在取钱之后结果就不一样,这就出现了线程安全问题
当出现线程安全问题的时候可以通过,让每个线程都访问自己的局部变量,或者在多个线程必须同时操作实例变量/静态变量时采用线程同步技术
我们将账户类稍作修改
public class BankAccount {
int balance = 10000; //账户余额(万元)
private static final Object OBJ = new Object(); //定义了一个常量
//取钱操作,约定每次取钱1000万
public void withdraw() {
synchronized (OBJ) { //经常使用一个常量对象作为锁对象
System.out.println(Thread.currentThread().getName() + "取钱前,查询余额:" + balance + "万元");
balance -= 1000;
System.out.println(Thread.currentThread().getName() + "取了1000万后余额:" + balance + "万元");
}
}
}
然后再运行测试类
这样就通过synchronized解决了一个简单的线程安全问题
介绍一下synchronized语法和工作原理:
synchronized(锁对象){
同步代码块
}
工作原理:
1)任意对象都可以作为锁对象,每个对象有一个内置锁
2)某一时刻,锁对象最多只能被一个线程持有
3)如果线程获得了锁对象后,会一直持有,知道执行完同步代码块后才释放
4)线程要执行同步代码块,必须先获得锁对象
场景描述:假设有线程A和线程B都想要执行同步代码块
1)线程A获得CPU执行权,获得锁对象后,开始执行同步代码块
2)线程A在执行同步代码块期间,CPU执行权被线程B抢走了,线程A转为就绪状态
3)线程B获得CPU执行权,也想要执行同步代码块,必须先获得锁对象,现在锁对象被线程A持有,线程B转到等待锁对象池中进行阻塞
4)线程A重新获得CPU执行权,执行完同步代码块后释放锁对象
5)等待锁对象池中的线程B获得了锁对象,转为就绪状态