多线程(三)
线程安全
什么时候数据在多线程并发的环境下会存在安全问题
- 多线程并发
- 有共享数据
- 共享数据有修改行为
满足以上3个条件之后,就会存在线程安全问题
同步机制
如何解决?线程排队执行(不能并发),专业术语叫做“线程同步机制”
用排队执行解决线程安全问题,这种机制被称为:线程同步机制。
注意:线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位。只有数据安全了,我们才可以谈效率,数据不安全,没有效率的事。
说到线程同步这块,涉及到两个专业术语:
-
异步编程模型:
线程T1和线程T2,各自执行各自的,T1不管T2,T2不管T1,谁也不需要等谁,其实就是多线程并发(效率较高)
-
同步编程模型:
线程T1和T2,在线程T1执行的时候,必须等待T2线程执行结束,或者说在T2线程执行的时候,必须等待T1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。(效率较低,线程排队执行)
异步就是并发,同步就是排队!!!
synchronized的写法:
- 同步代码块
synchronized(线程共享对象){
同步代码块;
}
-
在实例方法上使用synchronized。表示共享对象一定是this,并且同步代码块是整个方法体。
-
在静态方法上使用synchronized。表示找类锁,类锁永远只有1把。
对象锁:1个对象1把锁,100个对象100把锁。
类锁:100个对象,也可能只是1把类锁
例子1
package se5.threadsafe;
public class Test {
public static void main(String[] args) {
//创建账户对象(只创建一个)
Account act = new Account("act-001",10000);
//创建两个线程
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
//设置name
t1.setName("t1");
t2.setName("t2");
//启动线程取款
t1.start();
t2.start();
/**
* 结果:
* t1对act-001取款5000.0成功,余额5000.0
* t2对act-001取款5000.0成功,余额5000.0
*/
}
}
package se5.threadsafe;
/*
银行账户
不使用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题
*/
public class Account {
//账号
private String actno;
//余额
private double balance;
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款方法
public void withdraw(double money){
//t1和t2并发这个方法。。。(t1和t2是两个栈,两个栈操作堆中同一个对象)
//取款之前的余额
double before = this.getBalance();
//取款之后的余额
double after = before - money;
//在这里模拟一下网络延迟,100%出问题
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新余额
//思考:t1执行到这里了,但还没有执行这行代码,t2线程进来withdraw方法了,此时一定出问题。
this.setBalance(after);
}
}
package se5.threadsafe;
public class AccountThread extends Thread {
//两个线程必须共享同一个账户对象
private Account act;
//通过构造方法传递过来账户对象
public AccountThread(Account act){
this.act = act;
}
public void run(){
//run方法的执行表示取款操作
//假设取款5000
double money = 5000;
//取款
//多线程并发执行这个方法
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "对" + act.getActno() + "取款" + money + "成功,余额" + act.getBalance());
}
}
修改后
package se5.threadsafe2;
public class Test {
public static void main(String[] args) {
//创建账户对象(只创建一个)
Account act = new Account("act-001",10000)