• 线程基础 第二篇:多线程之间的同步


    序;  大家好,这次我们就来学一下线程之间的同步操作:

        一、 如果我们要使用多线程操作同一个对象或者数据时,那么就要先知道为何要使用同步? 

          java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如对同一个数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性准确性

       由此,我们可以得出同步的概念:

        所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。例如Window API函数SendMessage。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回的LRESULT值返回给调用者。

        在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性

       二、   如何使用同步?

        在网上简单的搜了一下,发现同步的方式有5-7种,其实同步来同步去,里面的原理是没有变化的,那么今天就简单来讲一种:

        1、用 synchronized 关键字修饰方法、代码块

          实现线程安全:synchronized

        (1方法加锁

          public synchronized void a(){

            //在该方法中可以访问共享的对象

          }

        (2代码块加锁

          public void b(){

            synchronized(共享对象){

            i++;

            }

          }

          注:同步是一种高开销的操作,因此应该尽量减少同步的内容。 通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。 

       

    三、如上所说,我们就接着上一篇的卖票系统来增加多个窗口卖票,也就是增加多个线程,来演示一下线程的同步:

         (一)演示背景:假设我们有一个卖票系统,共有50张票,原来只有一个窗口,现在因为近期生意火爆,顾客排队等待时间较长,所以现在要增加三个窗口,那么写到代码中就是增加三个线程(一个窗口代表卖票的一个线程,那选在就一共有四个线程)

         (二)代码的类与方法与上一篇的一样,不一样的就是在测试阶段,增加了三个线程,代码如下:

          1)实现Runnable接口的卖票的类  

    /**
    *@functon 卖票系统中的窗口 
    *@author 温煦(昵称:沉沦之巅)
    *@time 2017.12.1 
    */
    package TicketShop;
    //此类为实现Runnable的接口的方法来演示线程
    public class SaleTicketsbyRannable implements Runnable{
    
        //获得票的类
        public Tickets tic;
    
        //有参构造
        public SaleTicketsbyRannable(Tickets tic) {
            super();
            this.tic = tic;
        }
    
        //实现Runnable接口中的run方法,进行重写
        @Override
        public void run() {
            //while判断当票数为零时停止销售
            while(tic.getCount()>=0){
                sale();
            }
        }
        
        //卖票的方法(注意注意:这里的synchronized 关键字起这关键的作用,因为有它才能完成线程的同步,可以说是一大功臣了,哈哈)
        public  synchronized void sale() {
            //获取当前线程的名字,直观的看出是哪个线程
            String threadname = Thread.currentThread().getName();
            //判断当票数大于0时卖票
            if(tic.getCount()>0){            
                //打印输出卖的是第几张票
                System.out.println(threadname+":第"+tic.getCount()+"张票已售出!");
                //卖完之后要让票的总数减1
                tic.setCount(tic.getCount()-1);
                try {
                    //线程沉睡0.2秒,只是方便看演示效果
                    Thread.currentThread().sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{//当票数为0是输出“票已售空"
                //因为while还在循环,所以要减一,否则会进入“票已售空”无限死循环
                tic.setCount(tic.getCount()-1);
                System.out.println(threadname+"的票已售空!");
            }
        }
    
        //票类的set和get方法
        public Tickets getTic() {
            return tic;
        }
    
        public void setTic(Tickets tic) {
            this.tic = tic;
        }
    }

        

          2)票的类

    /**
     *@functon 卖票系统中的总票数 
     *@author 温煦(昵称:沉沦之巅)
     *@time 2017.12.1 
     */
    
    package TicketShop;
    
    //此类为要卖的票,所有要卖的票都要从这里取
    public class Tickets {
        //因是一个demo,所以票类里没有那么多的属性值
        //票的总数
        private int count;
    
        //有参构造
        public Tickets(int count) {
            super();
            this.count = count;
        }
        
        //无参构造
        public Tickets() {
            super();
        }
        
        //set和get方法
        public int getCount() {
            return count;
        }
        public void setCount(int count) {
            this.count = count;
        }
    }

         

         3)演示四个窗口卖票结果的测试类(注意注意:与上一篇不一样的地方就是这,也是最重要的部分)

    /**
     *@functon 卖票系统演示(用实现Rannble接口的方法)
     *@author 温煦(昵称:沉沦之巅)
     *@time 2017.12.1 
     */
    
    package TicketShop;
    
    public class TestSale {
    
        public static void  main(String[] args) {
            
            //new一个票的实体类,并给它50张票
            Tickets tic = new Tickets(50);
            //创建  
            SaleTicketsbyRannable str = new SaleTicketsbyRannable(tic);
            //因此对象没有继承Thread类所以不可以直接调用
            //需要new一个Thread,现在要new 4 个了,因为有四个窗口了哦
            Thread st1 = new Thread(str,"窗口1");//给每个new线程后,加个名字,来区分四个窗口
            Thread st2 = new Thread(str,"窗口2");
            Thread st3 = new Thread(str,"窗口3");
            Thread st4 = new Thread(str,"窗口4");
            //执行
            st1.start();
            st2.start();
            st3.start();
            st4.start();
        }
    }

      

          4)演示效果(因数据太多,中间就省略了哦)

    
    

      窗口1:第50张票已售出!
      窗口4:第49张票已售出!
      窗口4:第48张票已售出!
      窗口3:第47张票已售出!

    ...
    ...

    窗口4:第11张票已售出! 窗口1:第10张票已售出! 窗口4:第9张票已售出! 窗口4:第8张票已售出! 窗口3:第7张票已售出! 窗口3:第6张票已售出! 窗口2:第5张票已售出! 窗口3:第4张票已售出! 窗口3:第3张票已售出! 窗口3:第2张票已售出! 窗口4:第1张票已售出! 窗口4的票已售空! 窗口1的票已售空! 窗口3的票已售空! 窗口2的票已售空!

         在演示效果中,大家可以看到,四个卖票的窗口,同时卖的是同一种票,而且卖完之后不会在另外几个窗口内出现,虽然是同时开始售票,但是在内部机制里还是一个一个的 来执行的,里面的执行是没有顺序的,也就是说,哪个线程先获得资源,哪个线程就先执行,到售完票以后,若再次访问窗口,则会提示“票已售完”的信息!这就是线程之间的同步!

        好了,这就是本篇的线程同步,又到了跟大家sayGoodbay的时候了,在这里祝大家在学习Java的道路上越走越远!

        (本篇学完后,就表明你已经迈上了线程的第二节台阶,什么,你要问我一共有多少节台阶,嘻嘻,诚实的说,我也看不到头啊,哈哈,每一门知识都是博大精深的,诚然希望你学的越多越好!

        悄悄告诉你哦,第三篇是实现线程之间的通信,如果有兴趣就去看看吧!

  • 相关阅读:
    技术必备:推荐一款接口自动化测试数据校验神器
    提高GUI自动化测试稳定性解决方案
    新特性,推荐一款超强接口管理神器 Apifox
    测试开发:从0到1学习如何测试API网关
    接口自动化测试,一键快速校验接口返回值全部字段
    测试工程师必学:测试人员如何深入了解项目
    Django+Vue+Docker搭建接口测试平台实战
    如何开展线上全链路压测思路分享
    刚转行1年测试新手:学习Python编程经验实战分享
    测开新手:从0到1,自动化测试接入Jenkins学习
  • 原文地址:https://www.cnblogs.com/Wenxu/p/7944624.html
Copyright © 2020-2023  润新知