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

线程同步

一个经典的线程同步例子

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容