26 线程同步

线程同步

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成对象,等待前面线程使用完毕,下个线程再使用

 

由于同一个进程的多个线程共享同一块存储空间,带来方便的同时,也带来了访问的冲突问题,为了保证数据在方法中被访问的正确性,在访问时加入锁机制

synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放即可,存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起

  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题

  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题

 

三大不安全实例

每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

//不安全的买票
//线程不安全 有负数
public class UnsafeBuyTicket {
   public static void main(String[] args) {
       BuyTicket station = new BuyTicket();

       new Thread(station,"苦逼").start();
       new Thread(station,"惨").start();
       new Thread(station,"黄牛").start();

  }
}


class BuyTicket implements Runnable{
   //票
   private int ticketNum = 10;
   boolean flag = true;
   @Override
   public void run() {
       //买票
       while(flag){
           buy();
      }
  }

   private void buy(){
       if(ticketNum <= 0){
           flag = false;
           return ;
      }
       //模拟延时
       try {
           Thread.sleep(100);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       //买票
       System.out.println(Thread.currentThread().getName()+"拿到"+ticketNum--);
  }
}

//不安全的取钱
//有两个取钱
public class UnsafeBank {
   public static void main(String[] args) {
       Account account = new Account(100,"结婚的钱");

       Drawing you = new Drawing(account,50,"你");
       Drawing girlFriend = new Drawing(account,100,"girlFriend");

       you.start();
       girlFriend.start();


  }
}

class Account{
   int money;
   String name;

   public Account(int money, String name) {
       this.money = money;
       this.name = name;
  }
}

//银行模拟取款
class Drawing extends Thread {
   Account account;//账户
   //取了多少钱
   int drawingMoney;
   //现在手里有多少钱
   int nowMoney;

   public Drawing( Account account,int drawingMoney,String name){
       super(name);
       this.account = account;
       this.drawingMoney =drawingMoney;

  }


   @Override
   public void run() {
       //判断有没有钱
       if(account.money - drawingMoney < 0){
           System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
           return;
      }
       //sleep 可以放大问题的发生性
       try {
           Thread.sleep(1000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }

       //卡内余额 = 余额 - 你取的钱
       account.money = account.money - drawingMoney;
       //你手里的钱
       nowMoney = nowMoney + drawingMoney;

       System.out.println(account.name + "余额为"+account.money);
       //Thread.currentThread().getName() = this.getName()
       System.out.println(this.getName()+ "手里的钱"+ nowMoney);
  }
}

//线程的不安全
public class UnsafeList {
   public static void main(String[] args) {
       List<String> list = new ArrayList<String>();
       for (int i = 0; i < 10000; i++) {
           new Thread(()->{
               list.add(Thread.currentThread().getName());
          }).start();
      }
       try {
           Thread.sleep(2000);
      } catch (InterruptedException e) {

      }
       System.out.println(list.size());
  }

}

 

synchronized关键字

它包括两种用法:synchronized方法和synchronized块

synchronized控制对对象的访问,每个对象对应一把锁,每个synchronized方法都必须调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦被执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行

缺陷:若将一个方法申明为synchronized将会影响效率

修饰一个方法

被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

修饰一个静态的方法

其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

 

//不安全的买票
//线程不安全 有负数
public class UnsafeBuyTicket {
   public static void main(String[] args) {
       BuyTicket station = new BuyTicket();

       new Thread(station,"苦逼").start();
       new Thread(station,"惨").start();
       new Thread(station,"黄牛").start();

  }
}


class BuyTicket implements Runnable{
   //票
   private int ticketNum = 10;
   boolean flag = true;
   @Override
   public void run() {
       //买票
       while(flag){
           buy();
      }
  }

   // synchronized同步方法,锁的是this
   private synchronized void buy(){
       if(ticketNum <= 0){
           flag = false;
           return ;
      }
       //模拟延时
       try {
           Thread.sleep(100);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       //买票
       System.out.println(Thread.currentThread().getName()+"拿到"+ticketNum--);
  }
}

 

 

synchronized块

修饰一个代码块

被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;

同步块 synchronized(Obj){}

Obj称为同步监视器

  • Obj可以是任意对象,但是推荐使用共享资源作为同步监视器

  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class【反射中讲解】

同步监视器的执行过程

  1. 第一个线程访问,锁定同步监视器,执行其中代码

  2. 第二个线程访问,发现同步监视器被锁定,无法访问

  3. 第一个线程访问完毕,解锁同步监视器

  4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

//不安全的取钱
//有两个取钱
public class UnsafeBank {
   public static void main(String[] args) {
       Account account = new Account(100,"结婚的钱");

       Drawing you = new Drawing(account,50,"你");
       Drawing girlFriend = new Drawing(account,100,"girlFriend");

       you.start();
       girlFriend.start();


  }
}

class Account{
   int money;
   String name;

   public Account(int money, String name) {
       this.money = money;
       this.name = name;
  }
}

//银行模拟取款
class Drawing extends Thread {
   Account account;//账户
   //取了多少钱
   int drawingMoney;
   //现在手里有多少钱
   int nowMoney;

   public Drawing( Account 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;
          }
           //sleep 可以放大问题的发生性
           try {
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }

           //卡内余额 = 余额 - 你取的钱
           account.money = account.money - drawingMoney;
           //你手里的钱
           nowMoney = nowMoney + drawingMoney;

           System.out.println(account.name + "余额为"+account.money);
           //Thread.currentThread().getName() = this.getName()
           System.out.println(this.getName()+ "手里的钱"+ nowMoney);
      }
      }
      }



//线程的不安全
public class UnsafeList {
   public static void main(String[] args) {
       List<String> list = new ArrayList<String>();
       for (int i = 0; i < 10000; i++) {
           new Thread(()->{
               synchronized (list) {
                   list.add(Thread.currentThread().getName());
              }
          }).start();
      }
       try {
           Thread.sleep(2000);
      } catch (InterruptedException e) {

      }
       System.out.println(list.size());
  }
}

 

posted @   flypiggg  阅读(94)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示