java基础-多线程(5)

90 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天

线程同步

1.介绍

多个线程操作同一个资源

Snipaste_2022-10-03_23-51-29.png

当多个线程同时运行时,线程的调度由操作系统决定,程序本身无法决定。因此,任何一个线程都有可能在任何指令处被操作系统暂停,然后在某个时间段后继续执行。

这个时候,有个单线程模型下不存在的问题就来了:如果多个线程同时读写共享变量,会出现数据不一致的问题。

Snipaste_2022-10-03_23-51-55.png

形成的条件:

队列和锁

Snipaste_2022-10-03_23-55-27.png

2.不安全的线程案例

 //不安全买票
 public class Demo24_UnsafeBuyTicket {
     public static void main(String[] args) {
         BuyTicket buyTicket = new BuyTicket();
         new Thread(buyTicket, "张三").start();
         new Thread(buyTicket, "李四").start();
         new Thread(buyTicket, "王五").start();
     }
 }
 ​
 class BuyTicket implements Runnable {
     //票
     private int ticketNums = 10;
     boolean flag = true;
 ​
     @Override
     public void run() {
         //买票
         while (flag) {
             try {
                 buy();
             } catch (Exception e) {
                 e.printStackTrace();
             }
         }
     }
 ​
     //买票
     private void buy() {
         //判断是否有票
         if (ticketNums <= 0) {
             flag = false;
             return;
         }
         //延迟
         try {
             Thread.sleep(1);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
 ​
         //买票
         System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
     }
 }
 ​

3.同步方法

Snipaste_2022-10-03_23-57-10.png

同步方法:锁的是this

弊端

方法里面需要修改的内容才需要锁,锁的太多,会浪费资源。

 //安全买票
 public class Demo27_SafeBuyTicket {
     public static void main(String[] args) {
         BuyTicket1 buyTicket = new BuyTicket1();
         new Thread(buyTicket, "张三").start();
         new Thread(buyTicket, "李四").start();
         new Thread(buyTicket, "王五").start();
     }
 }
 ​
 class BuyTicket1 implements Runnable {
     //票
     private int ticketNums = 10;
     boolean flag = true;
 ​
     @Override
     public void run() {
         //买票
         while (flag) {
             try {
                 buy();
             } catch (Exception e) {
                 e.printStackTrace();
             }
         }
     }
 ​
     //synchronized 同步方法,锁的是this
     private synchronized void buy() {
         //判断是否有票
         if (ticketNums <= 0) {
             flag = false;
             return;
         }
         //延迟
         try {
             Thread.sleep(1);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
 ​
         //买票
         System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
     }
 }
 ​

4.同步块

从包含的代码量来说,同步代码块比同步方法小。我们可以把同步代码块比喻成是没上锁房间里的多了一层带锁的内门。

带锁的内门可以由用户自定义其钥匙,你可以用本房的钥匙,你也可以指定用另一个房子的钥匙才能开,这样的话,你要跑到另一栋房子那儿把那个钥匙拿来,并用那个房子的钥匙来打开这个房子的带锁的内门。

记住你获得的那另一栋房子的钥匙,并不影响其他人进入那栋房子没有锁的房间。

Snipaste_2022-10-04_00-02-08.png

锁的对象就是变量的量,需要增删改查的对象

 /**
  * 安全的取钱 同步块
  */
 public class Demo28_SafeBank {
     public static void main(String[] args) {
         Account1 account = new Account1(100, "结婚基金");
         Drawing1 you = new Drawing1(account, 50, "展堂");
         Drawing1 girlfriend = new Drawing1(account, 100, "sad");
         you.start();
         girlfriend.start();
     }
 }
 ​
 //账户
 class Account1 {
     int money;//余额
     String cardName;//卡名
 ​
     public Account1(int money, String cardName) {
         this.money = money;
         this.cardName = cardName;
     }
 }
 ​
 //银行:模拟取款
 class Drawing1 extends Thread {
     Account1 account;//账户
     int drawingMoney;//取金额
     int nowMoney;//你手里的钱
 ​
     public Drawing1(Account1 account, int drawingMoney, String name) {
         super(name);
         this.account = account;
         this.drawingMoney = drawingMoney;
     }
 ​
     //取钱
     @Override
     public void run() {
         //锁的对象就是变量的量,需要增删改查的对象
         synchronized (account) {
             //判断是否有钱
             if (account.money - drawingMoney < 0) {
                 System.out.println(Thread.currentThread().getName() + "余额不足,不能进行取钱");
                 return;
             }
             try {
                 Thread.sleep(1000);//放大问题的发生性
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             //卡内金额 = 余额-你的钱
             account.money = account.money - drawingMoney;
             //你手里的钱
             nowMoney = nowMoney + drawingMoney;
             System.out.println(account.cardName + "余额为:" + account.money);
             //this.getName()==Thread.currentThread().getName()
             System.out.println(this.getName() + "手里的钱:" + nowMoney);
         }
     }
 ​

总结

学完后对synchronized还是比较了解,了解线程安全的解决办法。