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){},但是这只是减小死锁发生概率.

posted @ 2013-03-16 16:31  伊秋  阅读(276)  评论(0编辑  收藏  举报