三 java------线程---线程同步

线程一些小知识的整理。

一:线程的同步
需求:写一个多线程程序实现铁路售票系统,最少4个售票点,注意线程同步!
技能: 引入线程同步---同步方法和同步代码块---synchronized   
  1.多线程的特点
     1.1 并发性---相当于同时运行
     1.2 随机性---运行那个线程不确定,谁先抢到CPU资源,谁先运行
  2.怎么消除线程的随机性----可以使用线程同步的方式。
      2.1 同步方法---也就是在方法上使用synchronized关键字进行修饰即可。
          格式:  权限修饰符   synchronized 返回值类型  方法名称(参数列表){
                     方法体;
                     [return  返回值;]
                }
          分析: 如果原来的普通方法,使用synchronized关键字修饰,变成同步方法,里面暗含一个同步锁---this(当前类对象)
      2.2 同步代码块---也就是使用synchronized关键字进行修饰的普通代码块
          格式: synchronized(同步锁){
                   要同步的代码;
               }
          分析: 关于同步代码块当中的同步锁有三种
               1.使用this----当前类对象可以当作是同步锁
               2.使用同步代码所在的类的字节码文件对象,作为同步锁----TrainTicketSellSystem.class
               3.使用共享资源来充当锁 ---- 例如: 火车票可以看成是共享资源  ---- 单独创建一个类,类中的属性是火车票
   3.编写的步骤
       3.1 创建一个类,继承Thread类,目的:创建对象---也就是创建线程对象
       3.2 创建类中的属性----火车票  int ticket =100;
       3.3 重写run()方法,因为run()方法时线程的主体
       3.4 创建四个线程对象,调用start方法
  
   发现: 每一个窗口,都卖了200张票,一共卖了1000张票
   发现: Thread-0  Thread-1这些是什么? 这些是线程的名称,如果没有对线程进行命名的化,JVM会自动为线程名称,命名的方式
        Thread-x  x是从0开始的
代码区:
public class TrainTicketSellSystem  extends Thread{
 
 //属性----成员变量   火车票
 int  ticket = 200;
 //重写run()方法
 public void run(){
  //run()方法里面,应该放的是线程的主体代码----售票
  //在售票之前,需要进行判断,如果大于零,说明有票可卖
  boolean flag = true;
  while(flag){
   if(ticket >0){
    ticket--;
    System.out.println(Thread.currentThread().getName()+"卖出第"+(200-ticket)+"张票");
   }else{
    System.out.println("火车票票数不足,请购买下一趟列车");
    flag = false;
   }
  }
  
 }
 public static void main(String[] args) {
  //创建线程对象,因为本类继承了Thread类,所以创建本类对象,也就是创建了线程对象
  TrainTicketSellSystem t1 = new TrainTicketSellSystem();
  TrainTicketSellSystem t2 = new TrainTicketSellSystem();
  TrainTicketSellSystem t3 = new TrainTicketSellSystem();
  TrainTicketSellSystem t4 = new TrainTicketSellSystem();
  TrainTicketSellSystem t5 = new TrainTicketSellSystem();
  
  t1.setName("窗口A");
  t2.setName("窗口B");
  t3.setName("窗口C");
  t4.setName("窗口D");
  t5.setName("窗口E");
  
  
  t1.start();
  t2.start();
  t3.start();
  t4.start();
  t5.start();
 }

}
二:同步方法
需求:写一个多线程程序实现铁路售票系统,最少4个售票点,注意线程同步!
技能: 引入线程同步---同步方法
  发现: 创建线程的方式之一,继承Thread,不能共享资源。
       分析: 属性----如果没有使用static修饰,则它从属于对象,只有对象开辟空间之后,属性才有具体的值
       例如: 创建了5个线程对象,每创建一个对象,都会在堆内存当中,为ticket开辟空间,初始化值都是200
       解决:第一种方法,是把成员变量变为静态变成员量(不推荐)
            第二种方法,把实现线程的方式,改为实现Runnable接口的形式,而不是继承Thread类(推荐它)
            
  发现: 不要把任何的代码都放到run()方法里面去,原因: 代码显得臃肿
       解决: 在单独创建一个方法,把核心代码放入到此方法中即可。
       
  发现: 在创建Runnable对象时,只需要创建一个即可,不要再创建5个啦
       原因: 如果创建了5个Runnable对象,和继承Thread类,启动线程,没有任何区别了!
            重点:实现Runnable接口,可以实现资源共享
       分析:面向对象多态机制
    包括: 对象的多态---- 向上转型
                     父类类型  父类引用 = new 子类类型(); ---发现父类的本质,其实就是一个子类对象!
        Runnable  r = new TrainTicketSellSystem();
                      因为子类存在一个属性---火车票 200,如果创建了五个Runnable  接口对象,也就是创建了5个TrainTicketSellSystem子类对象
                     所以:要创建一个Runnable接口对象,目的: 实现资源共享
                     
  发现: 解决了卖了1000张票的问题,但是出现了200张票,都是由同一个窗口售卖,其他窗口没有!
       原因:当某一个线程执行start()方法,会默认的调用run()方法,因为run()里面有一个同步方法---sell()
           当某一个线程执行此同步方法时,其他的线程必须等待此线程执行结束,才有可能进行执行sell();
           但是当其他线程执行sell方法时,火车票数已经 不满足需求,直接执行break,跳出循环!
       解决:方案1: 使用sleep()方法,但是发现,在线程同步的情况在,是不会释放锁,同时其他先也进不来!---不推荐
           方案2:  重点: 释放锁,让其他先也能参与!
                  释放锁---线程自动释放----只有当前线程执行完毕代码之后,会自动释放锁
                 步骤:
                   1.同步方法内的while()循环移出 ,目的: 不让同一个线程把所有的票给卖光
                   2.在run方法内,写一个while()循环,循环内部写入sell方法,同时要加以判断
                     判断票数是否<=0,如果不满足,说明200张票还没有买完,反之,200张票卖光了
代码区:
public class TrainTicketSellSystem  implements Runnable{
 
 //属性----成员变量   火车票
 int  ticket = 200;
 //重写run()方法
 public void run(){
  //run()方法里面,应该放的是线程的主体代码----售票
     while(true){
       sell();
       //把死循环,变为有限循环
       if(ticket<=0){ //如果满足,说明,没有票可卖了
        break;
       }
     }
   
 }
 //添加同步关键字---synchronized,使用普通变为同步方法---暗含的同步锁,是当前类对象,也就是说是this
 public  synchronized  void  sell(){
  //在售票之前,需要进行判断,如果大于零,说明有票可卖
       if(ticket >0){
    System.out.println(Thread.currentThread().getName()+"卖出第"+(201-ticket--)+"张票");
    try {
     Thread.sleep(100);
    } catch (InterruptedException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
    }
 }
 //创建主方法,目的: 创建线程对象
 public static void main(String[] args) {
  //1.创建Runnable接口对象
  Runnable r = new TrainTicketSellSystem();
  //2.因为Runnable接口对象,没有start方法,必须借助Thread类
  Thread t1 = new Thread(r,"窗口A");
  Thread t2 = new Thread(r,"窗口B");
  Thread t3 = new Thread(r,"窗口C");
  Thread t4 = new Thread(r,"窗口D");
  Thread t5 = new Thread(r,"窗口E");
  //3.启动线程---JVM会默认的调用run()方法---调用sell()方法
  t1.start();
  t2.start();
  t3.start();
  t4.start();
  t5.start();
 }
}
三:同步代码块--this(同步锁)
需求:写一个多线程程序实现铁路售票系统,最少4个售票点,注意线程同步!
技能: 引入线程同步---同步代码块
  1.同步代码块的格式
      synchronized(同步锁){
         要同步的代码;
      }
  2.关于同步代码块当中的同步锁!
     强调:同步锁只能是对象的形式,不能是基本数据类型!
      2.1 使用this----暗含的是当前类对象
            即可用到同步方法,也可以用到同步代码块。
      2.2 使用共享资源---推荐使用此锁
            共享资源必须是以对象的形式存在,不能是基本数据类型!
            如果在某个类当中,使用基本数据类型充当共享资源是,需要把基本数据类型---->类的形式
            此处并不是使用包装类,只是创建一个类,把这个资源(基本数据类型)放入到此类中即可。
      2.3 使用字节码文件对象
            字节码---二进制代码---那个文件中,存在的是以二进制为内容的----.class文件
            字节码文件对象,就是一个.class文件
            在后续章节当中,会学习反射机制----反射的源头类---Class
  发现: 每一个窗口各卖了一张票
  发现: 窗口卖的票数,远远的超出预定的票数
       原因: 因为在同步的同时,使用的是死循环
       解决: 把死循环改成有限循环
  发现: 在使用同步以及,循环语句时,窗口多卖了4张票
代码区:
public class TrainTicketSellSystem implements Runnable{
 
 //属性---成员变量
 int  ticket = 200;

 //重写run()方法
 public void run() {
  //while循环语句,目的: 让各个窗口,都在卖票
  while(true){
  
   //同步代码块锁,使用this充当同步锁
   synchronized(this){
    //加一个判断语句,如果票数小于等于0,说明票已经卖完
    if(ticket <=0){
     break;
    }
    //把要同步的代码,放入到此代码块中
    System.out.println(Thread.currentThread().getName()+"出售第"+(201-ticket--)+"张票");
    //sleep方法的作用:线程的切换
    try {
     Thread.sleep(100);
    } catch (InterruptedException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    } 
   }
  }  
 }
}
public class Test {
 public static void main(String[] args) {
  //1.创建Runnable接口对象
  Runnable r = new TrainTicketSellSystem();
  //2.创建Thread类对象
  Thread t1 = new Thread(r,"窗口A");
  Thread t2 = new Thread(r,"窗口B");
  Thread t3 = new Thread(r,"窗口C");
  Thread t4 = new Thread(r,"窗口D");
  Thread t5 = new Thread(r,"窗口E");
  //3.启动线程
  t1.start();
  t2.start();
  t3.start();
  t4.start();
  t5.start();
 }
}
四:同步代码块--共享资源(同步锁)
public class Ticket {
 private int ticketNum =200;

 public int getTicketNum() {
  return ticketNum;
 }

 public void setTicketNum(int ticketNum) {
  this.ticketNum = ticketNum;
 }
}
public class Test2 {
 public static void main(String[] args) {
  //1.创建Runnable接口对象
  Runnable r = new TrainTicketSellSystem2();
  //2.创建Thread类对象
  Thread t1 = new Thread(r,"窗口A");
  Thread t2 = new Thread(r,"窗口B");
  Thread t3 = new Thread(r,"窗口C");
  Thread t4 = new Thread(r,"窗口D");
  Thread t5 = new Thread(r,"窗口E");
  //3.启动线程
  t1.start();
  t2.start();
  t3.start();
  t4.start();
  t5.start();
 }
}
public class TrainTicketSellSystem2  implements Runnable{
 //使用共享资源来充当同步锁
 private Ticket t = new Ticket();
 //重写run()方法
  public void run() {
   //while循环语句,目的: 让各个窗口,都在卖票
   while(true){
   
    //同步代码块锁,使用this充当同步锁
    synchronized(t){
     //加一个判断语句,如果票数小于等于0,说明票已经卖完
     if(t.getTicketNum() <=0){
      break;
     }
     //把要同步的代码,放入到此代码块中
     System.out.println(Thread.currentThread().getName()+"出售第"+(201-t.getTicketNum())+"张票");
     //在Ticket类中,有get set方法,set方法的目的就是设置火车票数,因为在while循环内部,每循环一次就减1
     t.setTicketNum(t.getTicketNum()-1);
     //sleep方法的作用:线程的切换
     try {
      Thread.sleep(100);
     } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     } 
    }
   } 
  }
}
四:同步代码块--字节码对象(同步锁)
public class TrainTicketSellSystem3  implements Runnable{
 //使用共享资源来充当同步锁
// private Ticket t = new Ticket();
 private int ticket =200;
 //重写run()方法
  public void run() {
   //while循环语句,目的: 让各个窗口,都在卖票
   while(true){
   
    //同步代码块锁,使用this充当同步锁
    synchronized(TrainTicketSellSystem3.class){
     //加一个判断语句,如果票数小于等于0,说明票已经卖完
     if(ticket <=0){
      break;
     }
     //把要同步的代码,放入到此代码块中
     System.out.println(Thread.currentThread().getName()+"出售第"+(201-ticket--)+"张票");
     //sleep方法的作用:线程的切换
     try {
      Thread.sleep(100);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
   } 
  }
}
public class Test3 {
 public static void main(String[] args) {
  //1.创建Runnable接口对象
  Runnable r = new TrainTicketSellSystem3();
  //2.创建Thread类对象
  Thread t1 = new Thread(r,"窗口A");
  Thread t2 = new Thread(r,"窗口B");
  Thread t3 = new Thread(r,"窗口C");
  Thread t4 = new Thread(r,"窗口D");
  Thread t5 = new Thread(r,"窗口E");
  //3.启动线程
  t1.start();
  t2.start();
  t3.start();
  t4.start();
  t5.start();
 }
}
五:账户取款
需求:多个用户同时操作一个银行账户。
       每次取款400元,取款前先检查余额是否足够。
       如果不够,放弃取款
技能: 线程同步
  1.实现多线程的方式,有两种,一种是继承Thread类,一种是实现Runnable接口
    作业: 两者之间的区别!最好使用代码体现之
   大多数实现多线程,都使用实现Runnable接口的形式,原因: 实现资源共享;打破了类的单继承限制
  步骤:
    1.创建一个类,此类实现Runnable接口,并重写run()方法
    2.同步的方式,使用同步代码块,既然使用同步代码块,需要有一个同步监视器---同步锁(共享资源)
    3.创建一个类Account账户类,目的: 创建对象,充当同步锁; 取钱的方法和查询钱的方法,都存在于此类中。
    4.完善同步代码块
        在run()方法当中,编写同步代码块
        使用同步锁是Acount类对象
        在同步代码块中,应该先判断账户的钱和取钱的金额,是否相符
        切换用户: sleep()方法
    5.在测试类中,创建两个线程对象,定启动线程!
代码区:
 /*
 * Account类---账户类
 *    作用: 1.充当同步监视器---同步锁
 *         2.把取钱的方法以及查询前的方法,都定义在此类中!
 *           因为: 在同步代码块当中,会调用这两个方法!
 */
public class Account {
 //定义成员变量,目的: 显示银行卡的余额
 int  money = 600;
 //定义两个方法,一个方法是取钱,一个是查询
 public  void  withDraw(int money){
  this.money=this.money-money;
 }
 public  int   getMoney(){
  return money;
 }
}
public class AccountRunnable implements Runnable{
    //定义成员变量---目的: 同步锁   
 //作用域: 在整个类任何地方都有效
 private  Account accout = new Account();
 
 //重写run()方法
 public void run() {
  //使用同步代码块
  synchronized(accout){
   while(true){
    //先判断,银行卡余额是否满足,取款的要求
    if(accout.getMoney() >= 400){
     accout.withDraw(400);
     //取款
     System.out.println(Thread.currentThread().getName()+"取款成功,现在的余额为:"+accout.getMoney());
     if(accout.getMoney() < 400){
        break; 
     }
     
    }else{
     System.out.println(Thread.currentThread().getName()+"取款失败,余额不足,现在的余额为:"+accout.getMoney());
     break;
    }
   }
  }
 }
}
public class Tset {
 public static void main(String[] args) {
  //1.创建Runnable接口对象
  Runnable r = new AccountRunnable();
  //2.根据Thread类构造方法,传入Runnable接口对象,并对线程命名
  Thread t1 = new Thread(r,"高宇");
  Thread t2 = new Thread(r,"王梦凡");
  //3.启动线程
  t1.start();
  t2.start();   
 }
}

原博地址(也是我,O(∩_∩)O哈哈~):http://m15231417197.lofter.com/

练习代码地址(嘿嘿嘿):https://download.csdn.net/download/m15231417197/10716486

你可能感兴趣的:(线程)