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

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

作者: 木豚 | 来源:发表于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