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

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

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

      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的道路上越走越远!

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

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

原文地址:https://www.cnblogs.com/Wenxu/p/7944624.html