序; 大家好,这次我们就来学一下线程之间的同步操作:
一、 如果我们要使用多线程操作同一个对象或者数据时,那么就要先知道为何要使用同步?
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的道路上越走越远!
(本篇学完后,就表明你已经迈上了线程的第二节台阶,什么,你要问我一共有多少节台阶,嘻嘻,诚实的说,我也看不到头啊,哈哈,每一门知识都是博大精深的,诚然希望你学的越多越好!)
(悄悄告诉你哦,第三篇是实现线程之间的通信,如果有兴趣就去看看吧!)