zoukankan      html  css  js  c++  java
  • day11_多线程(多线程安全问题)

    同步代码块:

    /*
    通过分析,发现,打印出0,-1,-2等错票
    多线程的运行出现了安全问题.
    
    问题原因:
     当多条语句在操作多个线程的共享数据时,其中一个线程对多条语句只
     执行了一部分,还没有执行完,另一个线程参与进来执行.导致共享数据
     的错误.
    解决办法:
     对多条操作共享数据的语句,在同一时间间隔内只让其中一个线程都执行完,在执行过程中,其它
     线程不可以参与执行.
    
    Java对于多线程的安全问题提供了专业的解决方式
    就是同步代码块:
    synchronized(对象)//该锁或叫监视器
    {
        同步代码块
    
    }
    对象如同锁,持有锁的线程可以在同步中执行
    没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有锁
    
    
    同步前提:
    1.必须要有两个或者两个以上的线程
    2.多个线程必须使用同一把锁(使用的不同的锁不叫同步)
        
        必须保证同步中只能有一个线程在运行
    
    好处:解决了多线程的安全问题
    
    弊端:多个线程每个线程都需要判断锁,较为消耗资源.(开门上的锁)
    */
    class Ticket implements Runnable
    {
        private int ticket=3000;
        Object obj=new Object();
        public void run()//不能抛出interruptedExcetption,因为Runnable中的run方法没有抛出任何异常
        {
          while(true) 
            {
               synchronized(obj)
                {
                   if(ticket>0)
                    { 
                      try{Thread.sleep(10);}catch(Exception e){ }//模拟出错,让线程休眠(暂停)10秒,让cpu执行其它线程,未加同步代码块前
                     
                     System.out.println(Thread.currentThread().getName()+" "+ticket--);
                    //运行结果可能是全是一个线程在执行
                    //这是因为其它线程还没有抢到cpu,当抢到时,ticket<=0
                    //可以通过把ticket赋值大一点,让其它线程有执行到的机会
                    }
                }
            }
        }
    }
    class TicketDemo2
    {
        public static void main(String[] args)
        {
          Ticket t=new Ticket();
           new Thread(t).start();
           new Thread(t).start();
           new Thread(t).start();
           new Thread(t).start();
        }
    }
    /*
    分析:(出现0,-1,-2)
    while(true)
           if(ticket>0)
            {
             //可能出现的一种情况(安全隐患),当ticket=1时
             //Thread-0 执行到该位置(0线程变成就绪状态)->cpu切换到Thread-1执行到该位置->Thread-2同理->Thread-3同理
             //->cpu又切换到Thread-0(10ms时间已到),继续向下执行->打印出1
             //->切换到Thread-1->0
             //->切换到Thread-2->-1
             //->切换到Thread-3->-2
             try{Thread.sleep(10);}catch(Exception e){ }//模拟出错,让线程休眠(暂停)10ms,让cpu执行其它线程
             System.out.println(Thread.currentThread().getName()+" "+ticket--);
            }
    
    */
    
    /* 
        //在加上同步代码块后
        //以下为可能出现的一种情况:假设ticket已经为1
             Thread-0 Thead-1 Thread-2 Thread-3
                  
            
            //此时cpu切换到2线程,由于对象标志位为0,2线程无法执行
                如果cpu切换到1,3线程,依然无法执行同步代码块
              synchronized(obj)
                {  
                  
                  //当cpu切换到0线程执行,判断对象标志位,为1,可以进来执行
                  //0线程进来以后,把对象标志位置为0
                 //cpu切换到0线程,ticket>0
                   if(ticket>0)
                    { 
                   //执行到sleep方法,0线程暂停10ms,cpu去执行其他线程->其它线程依然无法执行同步代码块    
                     try{Thread.sleep(10);}catch(Exception e){ }
                    //10ms时间到时,当cpu再次切换到0线程时,打印1,ticket->0 
                     System.out.println(Thread.currentThread().getName()+" "+ticket--);
                    
                    }
                }
                //0线程执行完同步代码块对象标志位置1
        
    */
    
    /*
    经典同步问题:
      火车上 上厕所
    */

    Ticket2

    银行存钱小例子:

    /*
    需求:
    银行有一个金库.
    有两个储户分别存300元,每次存100,存3次.
    
    思路:
    两个储户->两个线程
    一个金库->对同一个金库进行操作
    
    目的:该程序是否有安全问题?
    
    找问题:
    1.首先 明确哪些代码是多线程运行代码
    2.其次 明确共享数据
    3.再次 明确多线程运行代码中哪些语句是操作共享数据的
    
    */
    class Bank
    {
      private int sum=0;
      Object obj=new Object();
      public void add(int n)//这个位置可以抛出异常,注意Bank
      {                     //没有继承任何父类(当然Object除外)
        
        synchronized(obj)//这里犯得一个错误,采用匿名对象(new Object())->每个线程用的不是一把锁
          {
           sum += n;
           try{Thread.sleep(10);}catch(Exception e){}//依然模拟出错
           System.out.println(Thread.currentThread().getName()+" sum="+sum);
          }
      }
    }
    class Depositor implements Runnable
    {
      //储户里面有存钱动作,被多个线程执行
      private Bank b=new Bank();
      public void run()//此位置不可抛出异常,因为Runnable中的run方法没有抛出任何异常
        {
        
    //
    也可以把同步代码块加在这里//但是出现一个问题:张三必须把所有钱存完,李四才能去存
         for(int i=0;i<3;++i)
          {
            //我也可以把同步代码块加在这里,问题是如果我在add中调用
            //一些其它方法,或定义一些其它变量,是没有必要同步的
             b.add(100);
         
          }
        }
    }
    class BankDemo
    {
        public static void main(String[] args)
        { 
           Depositor d=new Depositor();
           new Thread(d).start();
           new Thread(d).start();
          
        }
    }

    BankDemo

    以上运行结果有点巧合,也可能出现线程交替(我的是双核cpu)

    稍微改进一下:

    /*需求:
    银行有一个金库.
    有两个储户分别存300元,每次存100,存3次.
    */
    class Bank{
        private int sumMoney=0;
        public void add(int storeMoney){
          synchronized(Bank.class){
            sumMoney+=storeMoney;
            System.out.println(Thread.currentThread().getName()+"..."+sumMoney);
           }
        }
    }
    class StoreUser{
       private int storeMoney;
       public StoreUser(int storeMoney){//用户存入多少钱
         this.storeMoney=storeMoney;
       }
           public void storeMoney(Bank bank){//用户拿着钱,那么由用户发出存钱动作,存入哪个银行?
           bank.add(storeMoney);
        }
       
       public int getMoney(){
        return storeMoney;
       }
       public void setMoney(int storeMoney){
          this.storeMoney=storeMoney;
       }
    }
    class MainClass{
        public static void main(String[] args){
          final Bank bank=new Bank();
          final StoreUser[]  su={new StoreUser(100),new StoreUser(100)};
          for( int i=0;i<su.length;++i){
              final int nextUser=i;
              new Thread(new Runnable(){
              @Override
               public void run(){
                  for(int count=0;count<3;++count)     
                      su[nextUser].storeMoney(bank);
               }
             
           }).start();
        }
        
        }
    }

    同步函数:

    /*
    同步函数用的是哪一个锁呢?
      函数需要被对象调用.那么函数都有一个所属对象引用.就是this
    所以同步函数使用的锁是this
    
    通过该程序验证.
      使用两个线程来买票
      一个线程在同步代码块中.
      一个线程在同步函数中.
      都在执行卖票动作.
    */
    
    
    
    class Ticket implements Runnable
    {
        private int tick=100;
        Object obj=new Object();
        boolean flag=true;
        /*
        public void run()
        {
         
             
             while(tick>0)//卖完1000张"不卖了"
             synchronized(obj)
             {
                if (tick>0)
                {
                 try{Thread.sleep(10);}catch(Exception e){}
                 System.out.println(Thread.currentThread().getName()+" "+tick--);
                }
                
             }
        }
        
        */
      //方法二:利用同步函数加锁
        public void run()
        {
           if(flag)
            {
             
             while(tick>0)
                { 
                    synchronized(this)
                     {
                        if (tick>0)
                        {
                         try{Thread.sleep(10);}catch(Exception e){}
                         System.out.println(Thread.currentThread().getName()+" --code()-- "+tick--);
                         
                        } 
                        
                     }
                }
            
            }
            else
             while(tick>0)
                show();
             
        }
        private synchronized void show()
        {
                 if (tick>0)
                 {
                  try{Thread.sleep(10);}catch(Exception e){}
                  System.out.println(Thread.currentThread().getName()+" --show()-- "+tick--);
                 }
                
        }
    }
    class ThisLockDemo
    {
      public static void main(String[] args)
      {
       Ticket t=new Ticket();
       new Thread(t).start();
       try{Thread.sleep(10);}catch(Exception e){}
       t.flag=false;
       new Thread(t).start();
       //以上运行结果极有可能都为show()(当不使主线程暂停)
       //这是因为主线程代码开启0线程后->0线程处于就绪状态,cpu不一定切换到
       //该线程执行run()->cpu接着执行主线程,此时flag已经为false
       //->cpu切换到0线程执行else内容
       //->cpu切换到主线程开启1线程->依然执行else内容
       //这时加上sleep让主线程短暂暂停,让其他线程有执行机会
      }
    
    }
    /*
    以上打印结果中有0号票
    原因:两个线程用的不是一把锁(obj和this)
         没有实现同步
    这时,把线程①的锁换成this
    此时消除0号票
    验证:同步函数的锁为this,并且this指向new Ticket()
    */

    同步函数被静态修饰:

    /*
    如果同步函数被静态修饰后,使用的锁是什么?
    通过验证,不是this.因为静态方法中不可以定义this
    
    静态进内存时,没有本类对象,但是一定有该类对应的 字节码文件对象(类名.class)
    该对象的类型(所述的类)是Class(Class 类的实例表示正在运行的 Java 应用程序中的类和接口)
    
    静态的同步方法,使用的锁是该方法所在类的字节码文件对象.类名.class
    */
    
    
    
    class Ticket implements Runnable
    {
        private static int tick=100;
        boolean flag=true;
        Class c=Ticket.class;
        public void run()
        {
           if(flag)
            {
             
             while(tick>0)
                { 
                    synchronized(Ticket.class)//或者用c
                     {
                        if (tick>0)
                        {
                         try{Thread.sleep(10);}catch(Exception e){}
                         System.out.println(Thread.currentThread().getName()+"--code()-- "+tick--);
                         
                        } 
                        
                     }
                }
            
            }
            else
             while(tick>0)
                show();
             
        }
        private static synchronized void show()
        {
               if (tick>0)
                {
                 try{Thread.sleep(10);}catch(Exception e){}
                 System.out.println(Thread.currentThread().getName()+" --show()-- "+tick--);
                }
                
        }
    }
    class StaticLockDemo
    {
      public static void main(String[] args)
      {
       Ticket t=new Ticket();
       new Thread(t).start();
       try{Thread.sleep(10);}catch(Exception e){}
       t.flag=false;
       new Thread(t).start();
      
      }
    
    }

    又见单例设计模式:

    /*
    单例设计模式
    
    */
    //饿汉式
    /*
    class Single
    {
        private Single(){}
        private static final Single s=new Single();//加上final更严谨,不能改变s的引用
         public static Single getInstance()
        {
          return s;
        }
    }
    */
    //懒汉式
    class Single
    {
        private Single(){}
        private static Single s=null;
        /*
        public static synchronized Single getInstance()
        {
         if(s==null)
          //A执行到该位置,CPU切换B,B执行到该位置
         //cpu又切换到A执行,创建对到象
         //切换到B,又创建对象,因此使用同步函数
          s=new Single();
         return s;
        
        }
        */
        //使用同步函数,每个线程都需要判断对象标志位,效率很低
        public static Single getInstance()
        {
         //⑥当C线程执行,s已不为null,返回
         if(s==null)
            {//②CPU切换到B,s依然为null
              synchronized(Single.class)//该类所属的字节码文件
              {
                //⑤CPU切换到B,s不为null,返回
                if(s==null)
                 //①A线程执行到该位置
                  s=new Single();
                 //③A创建完对象
              }
             //④对象标志位置1
            }
          return s;
        
        }
        //利用同步代码块,多加一个判断,减少线程判断锁的次数
        //相对提高了效率
       
    }

    死锁:

    /*
    死锁:
    通俗点说:
    两个人一个人一根筷子,两人相互索要,谁也不给谁->都别想吃
     同步中嵌套同步
    
    */
    
    
    
    
    class Ticket implements Runnable
    {
        private static int tick=1000;
        boolean flag=true;
        Object obj=new Object();
        public void run()
        {
           if(flag)
            {
             
                while(tick>0)
                { 
                   synchronized(obj)
                   {
                     //(可能出现)0线程执行到此持有obj锁,要想执行show需要this锁
                     show(); 
                   
                   }
                }
            
            }
            else
             while(tick>0)
                show();
             
        }
        private synchronized void show()
        {     //1线程执行到此,持有this锁,需要obj锁继续执行
                 synchronized(obj)
                  {
                        if (tick>0)
                        {
                         try{Thread.sleep(10);}catch(Exception e){}
                         System.out.println(Thread.currentThread().getName()+" --code()-- "+tick--);
                         
                        } 
                        
                   }
        }
    }
    class DeadLockDemo
    {
      public static void main(String[] args)
      {
       Ticket t=new Ticket();
       new Thread(t).start();
       try{Thread.sleep(10);}catch(Exception e){}
       t.flag=false;
       new Thread(t).start();
      
      }
    
    }
    /*
    可能不出现死锁:当CPU把一个线程执行完,再去执行另一个线程
    (把ticket赋值大点使其发生死锁)
    */

    死锁小程序:

    class Test implements Runnable
    {
        private boolean flag;
        Test(boolean flag)
        {
         this.flag=flag;
        
        }
        public void run()
        {
             if(flag)
             {
                 synchronized(MyLock.locka)
                 {
                   System.out.println("if locka");
                   synchronized(MyLock.lockb)
                   {
                   System.out.println("if lockb");
                   
                   }
                 }
              }
            else
             {
                synchronized(MyLock.lockb)
                 {
                    System.out.println("else lockb");
                   synchronized(MyLock.locka)
                   {
                   System.out.println("else locka");
                   
                   }
                 }
                 
              
              
             }
        }
    }
    //把锁放在单独类里面
    class MyLock
    {
        static MyLock locka=new MyLock();
        static MyLock lockb=new MyLock();
    }
    class DeadLockDemo2
    {
      public static void main(String[] args)
      {
         new Thread(new Test(true)).start();
         new Thread(new Test(false)).start();
      }
    
    }

    DeadLock2 

    鉴于以上我在想,能否略有改动解决死锁,可以让0线程暂停一会,让cpu执行1线程.在if(flag)下加上try{Thread.sleep(10);}catch(Exception e){},但是这只是减小死锁发生概率.

  • 相关阅读:
    Elasticsearch 搜索的评分机制
    Elasticsearch-mapper 基于注解方式生成mapping(2.0以上)
    Elasticsearch 与 Mongodb 数据同步问题
    Hadoop入门进阶课程6--MapReduce应用案例
    Hadoop入门进阶课程5--MapReduce原理及操作
    Hadoop入门进阶课程4--HDFS原理及操作
    Hadoop入门进阶课程3--Hadoop2.X64位环境搭建
    使用命令行备份指定文件夹并保留最新N份
    Hadoop入门进阶课程2--Hadoop2.X 64位编译
    Hadoop入门进阶课程1--Hadoop1.X伪分布式安装
  • 原文地址:https://www.cnblogs.com/yiqiu2324/p/2963499.html
Copyright © 2011-2022 走看看