美文网首页
线程学习笔记(二)线程同步与线程控制

线程学习笔记(二)线程同步与线程控制

作者: 木豚 | 来源:发表于2017-04-12 14:39 被阅读0次

线程同步

一个经典的线程同步例子

一个账户有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()

相关文章

网友评论

      本文标题:线程学习笔记(二)线程同步与线程控制

      本文链接:https://www.haomeiwen.com/subject/dbdmattx.html