ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

线程同步(JAVA笔记-线程基础篇)

2020-06-09 22:01:28  阅读:265  来源: 互联网

标签:tickets 同步 JAVA 张票 笔记 线程 窗口 票数


在多线程应用程序中经常会遇到线程同步的问题。比如:两个线程A线程B可能会 “同时” 执行同一段代码,或修改同一个变量。而很多时候我们是不希望这样的。
这时候,就需要用到线程同步。

多线程引发的问题

为了演示多线程引发问题,我们模仿买票,写一个简单的小程序。

  • 实现Runnable模拟买票
    public class SellTicket implements Runnable {
          //有30张票
          private int tickets=30;
          public void run() {
              //写一个死循环,模拟在不断的卖票。
              while (true){
                  //票数大于零,代表还有票,继续卖。
                  //如果票数小于等于零,也就是没票了。跳出循环,停止卖票
                  if(tickets > 0){
                      sell();
                  }else{
                      break;
                  }
              }
          }
          //买票方法,模拟买票的动作
          public void sell(){
              //记录下现在的票数
              int oldNumber = tickets;
              //卖掉一张后的票数,--tickets代表票数减一,模拟卖掉了一张票
              int nowNumber = --tickets;
              System.out.println(Thread.currentThread().getName()+",卖出了第("+ oldNumber +")张票,还剩("+ nowNumber +")张票。");
          }
      }
    
  • 是线程模拟窗口买票
    public class MyTest {
          public static void main(String[] args) {
              //使用同一个免票实例对象
              //所以,由于下面所有的窗口都用的是这一个对象。所以他们的票也都是sellTicket的tickets属性。
              SellTicket sellTicket=new SellTicket();
    
              //模拟多个窗口同时买票,每个线程代表一个买票窗口
              //Tread()的第二个参数,代表线程名。用线程名,模拟窗口名。
              Thread window1=new Thread(sellTicket,"窗口-1");
              Thread window2=new Thread(sellTicket,"窗口-2");
              Thread window3=new Thread(sellTicket,"窗口-3");
              Thread window4=new Thread(sellTicket,"窗口-4");
              Thread window5=new Thread(sellTicket,"窗口-5");
              
              //各个窗口开始工作
              window1.start();
              window2.start();
              window3.start();
              window4.start();
              window5.start();
          }
      }
    
    > 输出:
      窗口-3,卖出了第(28)张票,还剩(27)张票。
      窗口-5,卖出了第(30)张票,还剩(29)张票。
      窗口-1,卖出了第(26)张票,还剩(25)张票。
      窗口-2,卖出了第(27)张票,还剩(26)张票。
      ...
      ...
      窗口-2,卖出了第(6)张票,还剩(5)张票。
      窗口-5,卖出了第(4)张票,还剩(3)张票。
      窗口-3,卖出了第(2)张票,还剩(1)张票。
      窗口-4,卖出了第(0)张票,还剩(-1)张票。
      窗口-2,卖出了第(1)张票,还剩(0)张票。
    

上面的例子模拟了多窗口买票,但是看,输出结果是不是又问题?怎么还有第(-1)张票。难道还有站票不成?当然不存在的,这是我们程序出现了问题。
这就是多线程同时操作统一参数的问题。也就是上面例子中SellTicket对象的private int tickets=30;属性。

p.s.我运行了好多遍都没出现错误的情况,后面给每个方法休眠了0.5秒才出现上面的错误结果。说明多线程不同步代码发生的错误不是百分之百的,只是有一定的概率。

为什么会出现上面这种情况?

众所周知,多线程的同步不是真的同步执行的。只是CPU切换运行线程所以看上去是几个线程同步执行。理解了这个概念往下看。(实在不懂的可以百度一下其他大佬的文章,我之后可能还会写一个笔记来记录-如果有必要的话)

  • 我们一下最后三个输出结果,我们发现出错的是窗口4,也就是第四个线程。
      ...以上的可以省略,当然上面的也可能出现问题。比如两个不同的窗口卖出了同一张票。但是我没运行出来这个结果。。。。
      窗口-3,卖出了第(2)张票,还剩(1)张票。
      窗口-4,卖出了第(0)张票,还剩(-1)张票。
      窗口-2,卖出了第(1)张票,还剩(0)张票。
    
  • 出错的代码在下面。
      //在这里检测当前票数
      if(tickets > 0){
          sell();
      }else{
          break;
      }
    
    代码运行步骤如下:
    1. 到最后还剩一张票的时候。这时候 “窗口-4的线程” 运行了。它查看了一下当前票数,发现还有一张。然后进入到if()方法准备运行下面的代码。

      sell();
      
    2. 但是,“窗口-4的线程” 还没来得及运行sell()方法把票减一,或者运行到sell()里面,还没来得及减去仅剩的一张票,这时候CPU把运行的权力送给了 “窗口-2的线程” 。此时票数还是1。

    3. “窗口-2的线程” 动作比较快,它很快的检测当前票数是“1”,并进入if()方法,然后运行sell();将票数减一。

    4. 这时候再到 “窗口-4的线程” 此时票数就只剩下0了,但是他还是得运行sell()。也就只能卖出了第(0)张票,还剩(-1)张票了。

Java线程同步

为了避免上面的情况,可以使用以下三个方法来解决。

  1. 同步代码块
  2. 同步方法
  3. 同步锁

同步代码块

直接上代码

synchronized (this){
    //这里的代码,只允许一个线程运行。
    //等一个线程运行结束,把锁交给下一个等待的线程运行。
}

说明
1.this: 这里的this是同步锁也叫同步监听对象。
2.this可以是任何对象。
3.但是一般把当前多线程,并发访问的共同资源当作同步锁。在例子中也就是sellTicket对象。所以在对象里面可以写程this

修改上面的例子

public void run() {
    //写一个死循环,模拟在不断的卖票。
    while (true){
        //票数大于零,代表还有票,继续卖。
        //如果票数小于等于零,也就是没票了。跳出循环,停止卖票
        synchronized (this.getClass()){
            if(tickets > 0){
                sell();
            }else{
                break;
            }
        }
    }
}

同步方法

同步方法,其实跟同步代码块差不多,不过这个是在方法上添加synchronized关键字。

public synchronized void sell(){
    //这里的代码,只允许一个线程运行。
    //等一个线程运行结束,把锁交给下一个等待的线程运行。
}

要在上面的例子中使用同步方法,需要改一下。把if()判断放到sell()方法里面。

public synchronized void sell(){
    //在sell()方法在判断下当前票数
    if(tickets > 0){
        //记录下现在的票数
        int oldNumber = tickets;
        //卖掉一张后的票数,--tickets代表票数减一,模拟卖掉了一张票
        int nowNumber = --tickets;
        System.out.println(Thread.currentThread().getName()+",卖出了第("+ oldNumber +")张票,还剩("+ nowNumber +")张票。");
    }
}

同步锁

以上两种方法,都需要一个关键字synchronized。同步锁需要用到一个接口。

public interface Lock {
    //生源n多代码,详细的去看源码。
}

接口里面有两个比较重要的方法。

/**
* 请求一个锁
* Acquires the lock. 
*/
void lock();
/**
* 释放锁
* Releases the lock.
*/
void unlock();

在这两个方法之间的代码都是同步的。
但是Lock只是个接口,没法使用。
JUC包给我们一个常用的实现类ReentrantLock。部分源码如下:

public class ReentrantLock implements Lock, java.io.Serializable {
    //感兴趣的可以去看看源码
}

修改我们的案例代码

public class SellTicket implements Runnable {
    //有30张票
    private int tickets=30;

    Lock lock=new ReentrantLock();

    public void run() {
        //写一个死循环,模拟在不断的卖票。
        while (true){
            //票数大于零,代表还有票,继续卖。
            //如果票数小于等于零,也就是没票了。跳出循环,停止卖票
            lock.lock();
            //synchronized (this.getClass()){
            if(tickets > 0){
                sell();
            }else{
                break;
            }
            //}
            lock.unlock();

        }
    }
    //买票方法,模拟买票的动作
    public synchronized void sell(){
            //记录下现在的票数
            int oldNumber = tickets;
            //卖掉一张后的票数,--tickets代表票数减一,模拟卖掉了一张票
            int nowNumber = --tickets;
            System.out.println(Thread.currentThread().getName()+",卖出了第("+ oldNumber +")张票,还剩("+ nowNumber +")张票。");
    }
}

瞎总结

  • 需要同步就要加上锁。代码锁了,其他线程就只能在门外等着。
  • 无论是同步块、同步方法、锁机制。都有一个范围。前两个是在大括号中间{},后者在两个方法中间lock();unlock();
  • 但是加上同步的时候,里面的代码就只能由一个线程一次执行完。这样就违反我们使用多线程的目的。(俗称效率低下)
  • 所以为了提高效率,我们要尽量想办法,把加锁的代码范围缩小,缩小,再缩小。(前提是程序不会因为多线程出问题,毕竟安全比效率重要)

标签:tickets,同步,JAVA,张票,笔记,线程,窗口,票数
来源: https://www.cnblogs.com/Eastry/p/13081157.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有