线程同步
一个经典的线程同步例子
一个账户有500元,一个用户取出账户的500元,同时另一个用户也在向账户取500元。会发生什么?
- 首先创建一个Account类,包含AccountNO账户的编号和balance账户的余额。
- 然后创建一个取钱的线程类
public class DrawThread extends Thread {
private Account account;
private double drawAmount;
public DrawThread(String name,Account account, double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
@Override
public void run() {
if (drawAmount <= account.getBalance()) {
System.out.println(this.getName()+" 取出"+drawAmount+"元");
account.setBalance(account.getBalance() - drawAmount);
System.out.println("账户余额:"+account.getBalance());
} else {
System.out.println("余额不足");
}
}
}
- 测试同时取500会发生什么
public class test {
public static void main(String[] args) {
Account ac = new Account("123", 500);
new DrawThread("刘一",ac,500).start();
new DrawThread("陈二",ac,500).start();
}
}
输出
刘一 取出500.0元
账户余额:0.0
陈二 取出500.0元
账户余额:-600.0
为什么会发生这样的结果?
这结果肯定不是银行想要的结果。原因是线程的运行是靠线程调度来运行的,所以不是a执行完执行b,而是同步执行a,b。所以a,b同时在余额在500时候调用账户类,同时做取出操作。
那么怎么解决呢?
可以引入同步监视器来法锁定代码,任何时候只能有一个线程可以获得监视器的锁定
public class DrawThread extends Thread {
private Account account;
private double drawAmount;
public DrawThread(String name,Account account, double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
@Override
public void run() {
//同步监视器锁定
synchronized (account) {
if (drawAmount <= account.getBalance()) {
System.out.println(this.getName()+" 取出"+drawAmount+"元");
account.setBalance(account.getBalance() - drawAmount);
System.out.println("账户余额:"+account.getBalance());
} else {
System.out.println("余额不足");
}
//代码执行完解除锁定
}
}
}
输出
刘一 取出500.0元
账户余额: 0.0
余额不足
这样就是线程安全。
线程安全的定义:
该类的对象可以被多个线程安全地访问
每个线程调用该对象的任意方法之后都将得到正确的结果
每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态
锁
Lock提供了比synchronized方法更广泛的锁定操作,允许灵活结构
public class Draw {
private Account account;
private String name;
private final ReentrantLock lock = new ReentrantLock();
public Draw(String name,Account account) {
this.name = name;
this.account = account;
}
public void drawMoney(double money) {
//同步监视器锁定
lock.lock();
try {
if (money <= account.getBalance()) {
System.out.println(name + " 取出"+money+"元");
account.setBalance(account.getBalance() - money);
System.out.println("账户余额:"+account.getBalance());
} else {
System.out.println("余额不足");
}
//代码执行完解除锁定
} finally {
lock.unlock();
}
}
}
测试类
public class test {
public static void main(String[] args) {
Account ac = new Account("123", 500);
Draw d = new Draw("张三", ac);
new Thread(() -> {
d.drawMoney(500);
}).start();
new Thread(() -> {
d.drawMoney(500);
}).start();
}
}
输出
张三 取出500.0元
账户余额: 0.0
余额不足
死锁
倆个线程相互等待对方释放同步监视器时会发生死锁
public class test {
public static void main(String[] args) {
A a = new A();
B b = new B();
new Thread(() -> {
a.Afun(b);
}).start();;
new Thread(() -> {
b.Bfun(a);
}).start();
}
}
class A {
public synchronized void Afun(B b){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
b.print();
}
public synchronized void print(){
System.out.println("调用A线程");
}
}
class B {
public synchronized void Bfun(A a){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
a.print();
}
public synchronized void print(){
System.out.println("调用B线程");
}
}
死锁发生的原因
当a调用aFun()
方法时锁定了a然后睡眠让出了资源让b执行,同样b调用bFun()
锁住了b,这时a唤醒调用b的print()
方法,但是被b锁定,同样事情发生在b上,造成死锁.
线程通信
使用Object类中的notify()
,wait()
,notifyAll()
方法让线程之间进行通信,但这些方法必须通过同步监视器调用
wait()
:导致当前线程等待,直到其他线程调用notify方法,或者设置等待时间。
notify()
:唤醒在此同步监视器上等待的单个线程,如果有多个线程在随机唤醒一个。
notifyAll()
:唤醒在此同步监视器上等待的所有线程
账户类
public class Account {
private String accountNO;
private double balance;
//用flag来定义是存是取
private boolean flag;
public String getAccountNO() {
return accountNO;
}
public void setAccountNO(String accountNO) {
this.accountNO = accountNO;
}
public double getBalance() {
return balance;
}
public Account(String accountNO, double balance) {
super();
this.accountNO = accountNO;
this.balance = balance;
}
//取钱
public synchronized void draw(double number) {
try {
if (flag) {
System.out.println(Thread.currentThread().getName() + "取出:" + number);
balance -= number;
System.out.println("剩余余额:" + balance);
flag = false;
this.notifyAll();
} else {
this.wait();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//存钱
public synchronized void deposit(double number){
try {
if (!flag) {
System.out.println(Thread.currentThread().getName() + "存入:" + number);
balance += number;
System.out.println("剩余余额:" + balance);
flag = true;
this.notifyAll();
}
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
取钱线程
public class Draw implements Runnable{
private Account account;
private double money;
public Draw(Account account,double Dmoney) {
this.account = account;
this.money = Dmoney;
}
@Override
public void run() {
for (int i = 0; i < 50; i++){
account.draw(money);
}
}
}
存钱线程
public class Deposit implements Runnable{
private Account account;
private double Dmoney;
public Deposit(Account account,double Dmoney) {
this.account = account;
this.Dmoney = Dmoney;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
account.deposit(Dmoney);
}
}
}
测试
public class Deposit implements Runnable{
private Account account;
private double Dmoney;
public Deposit(Account account,double Dmoney) {
this.account = account;
this.Dmoney = Dmoney;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
account.deposit(Dmoney);
}
}
}
输出
刘一存入: 500.0
剩余余额: 1500.0
张三取出: 500.0
剩余余额: 1000.0
刘一存入: 500.0
剩余余额: 1500.0
陈二取出: 500.0
剩余余额: 1000.0
...循环...
使用lock
对象保证同步需要使用Condition
类来控制线程通信
实例化Conditon
,该类会提供三个方法await()
,signal()
,signalAll()
。分别对应wait()
,notify()
,notifyAll()