多线程-线程同步(五)

多线程-线程同步(五)

  1. 多线程的形成条件:队列+锁(为了安全)

  2. 线程同步安全,我们先来看下线程的不安全

    • 线程不安全的例子一:

      //不安全的买票
      //线程不安全,有负数
      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 ticketNums=10;
          boolean flag=true;//外部停止方式
          @Override
          public void run() {
              
              
              //买票
              while (flag){
              
              
                  try {
              
              
                      buy();
                  } catch (InterruptedException e) {
              
              
                      e.printStackTrace();
                  }
              }
      
          }
      
          private void buy() throws InterruptedException {
              
              
              //判断是否有票
              if (ticketNums<=0){
              
              
                  flag =false;
                  return;
              }
              //模拟延时
              Thread.sleep(100);
              //买票
              System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
          }
      }
      
    • 线程不安全的例子一,出现-1的情况原理图:

在这里插入图片描述

  • 线程不安全的例子二:

    //不安全的取钱
    //两个人去银行取钱,账户
    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);
        }
    }
    
  • 线程不安全的例子二,出现余额-50的情况原理图:

在这里插入图片描述

  • 线程不安全的例子三:

    import java.util.ArrayList;
    import java.util.List;
    
    //线程不安全的集合
    public class UnsafeList {
          
          
        public static void main(String[] args) {
          
          
            List<String> list=new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
          
          
                new Thread(()->{
          
          //不安全的原因:因为2个线程同一个瞬间操作了同一个位置,把两个数组添加了同一个位置就把它覆盖掉了,覆盖掉了,元素就会少,少的元素就是这么来的
                    list.add(Thread.currentThread().getName());
                }).start();
            }
            try {
          
          
                Thread.sleep(3000);//让他睡3秒,放大问题的发生性,还是打印不到1w个线程
            } catch (InterruptedException e) {
          
          
                e.printStackTrace();
            }
            System.out.println(list.size());//线程的大小没能够按照理论的打印1w个线程
        }
    }
    
    
  1. 线程不安全的原因

    每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。说白了,就是线程的内存都是各自的,每个线程其实都会出现系统的内存有多少,就会出现内存为负的情况,这也就导致了线程的不安全。

  2. 解决线程不安全的问题是:用同步方法

    • 我们针对这个方法提出了一套机制,这套机制就是synchronized关键字,他包括两种方法:synchronized方法和synchronized块

      同步方法:public synchronized void method(int args){}

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

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

在这里插入图片描述

  • 同步块:synchronized(Obj){}
  • Obj称之为同步监视器
    • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class[反射中讲解]
  • 同步监视器的执行过程:和synchronized方法的原理是一样的
  1. 解决线程不安全的例子一,在buy()方法那里加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 ticketNums=10;
        boolean flag=true;//外部停止方式
        @Override
        public void run() {
          
          
            //买票
            while (flag){
          
          
                try {
          
          
                    buy();
                } catch (InterruptedException e) {
          
          
                    e.printStackTrace();
                }
            }
    
        }
    
        //synchronized 同步方法,锁的是this
        private synchronized void buy() throws InterruptedException {
          
          
            //判断是否有票
            if (ticketNums<=0){
          
          
                flag =false;
                return;
            }
            //模拟延时
            Thread.sleep(100);
            //买票
            System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
        }
    }
    
  2. 解决线程不安全的例子二,锁的是account的一个同步块才能解决。

    //不安全的取钱
    //两个人去银行取钱,账户
    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;
        }
    
        //取钱
        //synchronized 默认锁的是this
        @Override
        public  void run() {
          
          
            synchronized (account){
          
          //这里只有锁的是account的同步块才有用,锁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);
            }
            }
    
    }
    
  3. 解决线程不安全的例子三,用一个同步块锁list。

    import java.util.ArrayList;
    import java.util.List;
    
    //线程不安全的集合
    public class UnsafeList {
          
          
        public static void main(String[] args) {
          
          
            List<String> list=new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
          
          
                new Thread(()->{
          
          //不安全的原因:因为2个线程同一个瞬间操作了同一个位置,把两个数组添加了同一个位置就把它覆盖掉了,覆盖掉了,元素就会少,少的元素就是这么来的
                   synchronized (list){
          
          
                       list.add(Thread.currentThread().getName());
                   }
                }).start();
            }
            try {
          
          
                Thread.sleep(3000);//让他睡3秒,放大问题的发生性,还是打印不到1w个线程
            } catch (InterruptedException e) {
          
          
                e.printStackTrace();
            }
            System.out.println(list.size());//线程的大小没能够按照理论的打印1w个线程
        }
    }
    
    
  4. 补充:JUC安全类型的集合,也能解决一些线程安全的问题。

import java.util.concurrent.CopyOnWriteArrayList;

//测试JUC安全类型的集合
public class TestJUC {
    
    
    public static void main(String[] args) {
    
    
        //concurrent并发包下写好的,安全的ArrayList
        CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
    
    
            new Thread(()->{
    
    
                list.add(Thread.currentThread().getName());
            }).start();
        }

        try {
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(list.size());

    }
}

猜你喜欢

转载自blog.csdn.net/Xun_independent/article/details/114992908
今日推荐