写点什么

【Java 深入学习】一个关于“锁”的程序 - 中

作者:Geek_65222d
  • 2022-10-21
    河南
  • 本文字数:2114 字

    阅读完需:约 7 分钟


与前文衔接的地方

可以看到线程 1 已经卖到了第 82 张票但此时线程 0 又卖了一次之前的第 100 张票,它的逻辑是这样的:最开始线程 1 运行了 System.out.printf(“%s 线程正在卖出第 %d 张票\n”,Thread.currentThread().getName(),tickets);语句但是并没有减 1,然后切换到了线程 0,然后线程 0 已经加载了 System.out.printf(“%s 线程正在卖出第 %d 张票\n”,Thread.currentThread().getName(),tickets);语句但此时还没有来的及输出(注意:因为输出语句已经加载好 所以此时要输出的应该是 Thread-0 线程正在卖出第 100 张票 只是没有来得及输出),之后又切换到了线程 1 继续从上次线程 1 运行的地方运行 此时运行 tickets–;,然后一直到 Thread-0 线程正在卖出第 100 张票之前都是线程 1 在正常运行 运行到 Thread-1 线程正在卖出第 82 张票,此时切换到线程 0 因为之前线程 0 已经加载了==System.out.printf(“%s 线程正在卖出第 %d 张票\n”,Thread.currentThread().getName(),tickets);所以继续从这里运行 所以接下来运行 System.out.printf(“%s 线程正在卖出第 %d 张票\n”,Thread.currentThread().getName(),tickets);语句输出 Thread-0 线程正在卖出第 100 张票 但是此时的票数实际上是 81 然后线程 0 运行 tickets–==后 此时票数是 80,所以之后线程 0 才会输入 Thread-0 线程正在卖出第 80 张票,那么第 81 张呢?其实第 81 张也是和第 100 张的情况一样,第 81 张被线程 1 的输出语句加载但还没有输出 但是之后 再次切换到线程 1 时会再次输出 81。

线程同步

知识了解

线程同步实际上是通过 synchronized 关键字实现的,它的作用是规定一个锁 这个锁可以锁定一块代码段也可以锁定一个方法, 这个锁 锁住一个公共对象 只有拿到这个锁的线程才可以运行锁内部的代码且不可被打断 直到这块代码运行完毕 释放锁 之后所有线程再来争抢锁。这样做的目的是为了 公共资源部分的代码在同一时刻只能由一个线程运行,避免线程不安全。对于卖票程序来说 它们的公共资源的就是 票池 锁住的部分就是卖票和票数减一。实质上 锁 锁住的对象叫做监听器 锁住对象的动作叫做锁定。

代码示例

class A implements Runnable{  public static int tickets = 10000;//写法一的话 static可以不用加 但写法二必须加  String str = new String("aa");//a  这样写 只有写法一正确  //static String str = new String("aa"); //b  这样写 写法二写法一都正确  public A()  {    System.out.println(this);    }  public void run()  {    while(true)    {      //synchronized(this)//这样写 只有写法一正确      synchronized(str)//这样写写法一一定正确,但写法二 只有在b的情况下才能正确      {        if(tickets>0)        {          System.out.printf("%s线程正在卖出第%d张票\n",Thread.currentThread().getName(),tickets);          tickets--;                }        else        {          break;        }      }    }    }  }
public class TestTickets_2{ public static void main(String[] args) { A aa=new A(); Thread t1=new Thread(aa); Thread t2=new Thread(aa); t1.start(); t2.start();//写法一 /*A aa1=new A(); Thread t1=new Thread(aa1); t1.start(); A aa2=new A(); Thread t2=new Thread(aa2); t2.start();//写法二*/ } }
复制代码


PS:synchronized 总结:格式:synchronized(类对象名 aa){同步代码块}synchronized(类对象名 aa)的含义是:判断 aa 是否已经被其他线程霸占,如果发现已经被其他线程霸占,则当前线程陷入等待,如果发现 aa 没有被其他线程霸占,则当前线程霸占注 aa 对象,并执行 3 行的代码同步块,在当前线程执行 3 行代码时,其他线程将无法再执行 3 行的代码(因为当前线程以及霸占了 aa 对象),当前线程执行完 3 行的代码后,会自动释放对 aa 对象的霸占,此时其他线程会相互竞争对 aa 的霸占,最终 cpu 会选择其中的某一个线程执行


补充:霸占的专业术语叫做锁定,霸占住的那个对象专业术语叫做监听器,监听器可以是 this 指向的那个对象也可以是类里的其他对象。


锁定类似于 有 n 个人上厕所 有一个厕所牌 只有当某人拿到厕所牌才可以上厕所,关键点在于只有一个厕所牌 即厕所牌是公共的,区别在于是哪个人拿了厕所牌 则锁定那个人的厕所牌 如果上完厕所则 其他人再争抢厕所牌。


当修饰代码块时需要(类对象名 aa),当修饰方法时 则不需要只需要在方法前加上 synchronized 不需要括号 默认监听器是 this。


当在运行同步代码块时,线程是可以随机改变的 只不过因为线程的监听器被锁定 导致其他的线程的监听器无法被锁定 导致只有监听器被锁定的那个线程运行完同步代码块后 把监听器释放 才能让其他线程的监听器取竞争。


当在创建线程时,只有写法一是对的,写法二虽然也创建了两个线程 但这两个线程的 synchronized 没有锁定的关系,依旧会出现代码不同步的问题。


如果保证写法二也是正确的 则必须保证 有唯一的厕所牌 即 公共对象,这个公共对象不能是 this 因为 this 会指向不同的对象,所以这种情况下 必须用一个公共的类对象 才能保证写法二正确,但写法一则不用考虑 因为这个的公共对象就是 aa


推荐使用写法一


结果:


测试了一万张票,没有出现错误。

发布于: 刚刚阅读数: 3
用户头像

Geek_65222d

关注

还未添加个人签名 2022-09-09 加入

还未添加个人简介

评论

发布
暂无评论
【Java深入学习】一个关于“锁”的程序-中_十月月更_Geek_65222d_InfoQ写作社区