首页 > 其他分享 >线程安全问题

线程安全问题

时间:2024-01-29 14:11:22浏览次数:15  
标签:Thread class 问题 安全 MyThread 线程 new ticket public

需求:某电影院目前正在上映国产大片,共100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

public class MyThread extends Thread{
  //表示这个类的所有对象都共享ticket
  static int ticket=0; //0~99
  
  @Override
  public void run(){
    while(true){
      if(ticket<100){
        try{
          Thread.sleep(100);
        }catch(InterruptedException e){
          e.printStackTrace();
        }
        ticket++:
        System.out.println(getName()+"正在卖第"+ticket+"张票");
      }else{
        break;
      }
    }
  }
}

public static void main(String[] args){
  //创建线程对象
  MyThread t1 = new Thread();
  MyThread t2 = new Thread();
  MyThread t3 = new Thread();

  //起名字
  t1.setName("窗口1");
  t2.setName("窗口2");
  t3.setName("窗口3");

  //开启线程
  t1.start();
  t2.start();
  t3.start();
}

此时的代码会有一个问题,3个窗口的票数不是多了就是重复了
多个线程操作同一个数据时,就会产生问题
问题1:相同的票出现了多次
问题2:出现了超出范围的票

原因:线程执行时,有随机性,多个线程操作同一个共享变量
如果线程执行到ticket++;还没有打印,cpu的执行权就被抢走了,就可能会打印相同的数据
//本地内存->主内存

可以用同步代码块解决
同步代码块:把操作共享数据的代码锁起来
格式

synchronized(锁对象){
  操作共享数据的代码
}

//锁对象没有要求,但是要确保唯一
//例如static Object obj =new Object();
特点:默认锁打开,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开

解决后的代码

public class MyThread extends Thread{
  //表示这个类的所有对象,都共享ticket数据
  static int ticket =0;
  
  //锁对象,一定要是唯一的
  static Object obj = new Object();

  @Override
  public void run(){
    while(true){
      synchronized(obj){
        if(ticket<100){
          try{
            Thread.sleep(10);
          }catch(InterruptedException e){
            e.printStackTrace();
          }
          ticket++:
          System.out.println(getName()+"正在卖第"+ticket+"张票");
        }else{
          break;
        }
      }
    }
  }
}

同步代码中的小细节
1.写在循环里面
2.锁对象,一定要是唯一的
一般书写为当前类的字节码文件
格式:类名.class

同步方法
把synchronized关键字加到方法上
格式:
修饰符 synchronized 返回值类型 方法名(方法参数){...}

特点1:同步方法时锁住方法里面的所有代码
特点2:锁对象不能自己指定
非静态:this
静态:当前类的字节码文件对象 类型.class

技巧:同步代码块完成后->写到同步方法

public class ThreadDemo{
  public static void main(String[] args){
    /*需求:
    *100张票,3个窗口卖票 利用同步方法
    *技巧: 同步代码块
    */
    
    MyRunnable mr = new MyRunnable();
    Thread t1 = new Thread(mr);
    Thread t2 = new Thread(mr);
    Thread t3 = new Thread(mr);
    
    t1.setName("窗口1");
    t2.setName("窗口2");
    t3.setName("窗口3");

    t1.start();
    t2.start();
    t3.start();
  }
}

public class MyRunnable implements Runnable{
   int ticket=0;
    @Override
    public void run(){
      //1.循环
      //2.同步代码块(同步方法)
      //3.判断共享数据是否到了末尾,如果到了末尾
      //4.判断共享数据是否到了末尾,如果没有到末尾

      while(true){
        synchronized(MyRunnable.class){
          if(ticket==100){
              break;
          }else{
            try{
              Thread.sleep(10);
            }catch(InterruptedException e){
              e.printStackTrace();
            }
            ticket++;
            System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
          }
        }
      }
    }
}

//选中然后ctrl+alt+m就可以抽取成一个方法

//抽取方法后
public class MyRunnable implements Runnable{
  int kicket=0;

  @Override
  public void run(){
    //1.循环
    while(true){
      //2.同步方法
      if(method())break;
    }
  }

  //非静态方法 锁对象是this
  privare synchronized boolean method(){
    //3.判断共享数据是否到了末尾,如果到了末尾
    if(ticket==100){
      return true;
    }else{
      //4.判断共享数据是否到了末尾,如果没有到末尾
      try{
        Thread.sleep(10);
      }catch(InterruptedException e){
        e.printStackTrace();
      }
      ticket++;
      System.out.println(Thread.currentThread().getName()+"在卖第"+ticket+"张票");
    }
  }
  return false;
}

StringBuild和StringBuffer的方法是一样的
但是StringBuffer的方法都加上了synchronized都是同步方法
如果单线程可以用StringBuild,多线程用StringBuffer

Lock锁
虽然可以理解同步代码块和同步方法的锁对象问题
但是并没有直接看到锁
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock实现提供比使用synchronized方法和语句,可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
void lock() 获得锁
void unlock() 释放锁
手动上锁,手动释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法
ReentrantLock() 创建一个RenntrantLock的实例

public class MyThread extends Thread{
  static int ticket = 0;
  static Lock lock = new ReentrantLock();

  @Override
  public void run(){
    //1.循环
    while(true){
      //2.同步代码块
      //synchronized(MyThread.class)
      lock.lock();
      //3.判断
      try{
        Thread.sleep(10);
      }catch(InterruptedException e){
        e.printStackTrace();
      }

      ticket++;
      System.out.println(getName()+"正在卖第"+ticket+"张票");
    }
    lock.unlock();
  }
}

public class ThreadDemo{
  public static void main(String[] args){
    MyThread t1 = new MyThread();
    MyThread t2 = new MyThread();
    MyThread t3 = new MyThread();

    t1.setName("窗口1");
    t2.setName("窗口2");
    t3.setName("窗口3");

    t1.start();
    t2.start();
    t3.start();
  }
}

此时还会有一个问题,虽然票卖完了,但是程序不会停止
原因是第100次之后结束循环后,锁还没有关
解决办法用try{...}catch{...}finally{关锁}
finally一定会被执行

更新后的代码

public class MyThread extends Thread{
  static int ticket = 0;
  static Lock lock = new ReentrantLock();
  
  @Override
  public void run(){
    //1.循环
    while(true){
      //2.同步代码块
      lock.lock();
      try{
        //3.判断
        if(ticket==100){
          break;
        }else{
           //4.判断
           Thread.sleep(10);
            ticket++;
            System.out.println(getName()+"在卖第"+ticket+"张票");
        }
      }catch(InterruptedException e){
        e.printStackTrace();
      }finally{
        lock.unlock();
      }
    }
  }
}

死锁(应该避免的错误)
死锁:指的是两个或多个线程无限期地互相等待对方所持有的资源,导致程序无法继续执行下去的状态

1.大锁套小锁可以解决死锁的循环,但是性能更低
2.最好的办法就是不要嵌套两个锁

标签:Thread,class,问题,安全,MyThread,线程,new,ticket,public
From: https://www.cnblogs.com/zhao-zong-yu-hai/p/17991276

相关文章

  • 线程池参数千万不要这样设置,坑得我整篇文章都写错了,要注意!
    你好呀,我是歪歪。先给大家道个歉:上周不是发布了这篇文章嘛:《三个烂怂八股文,变成两个场景题,打得我一脸懵逼。》其中第一个关于线程池的场景,经过读者提醒可能有问题,我又一次用尽浑身解数分析了一波,发现之前确实分析的不对。这个案例真的是再一次深入的刷新了我对于线程池运行过......
  • LearnOpengl_纹理初见问题
    在跟随learnOpengl教程绘制纹理时发现了一个问题。原本彩色纹理绘制出来却是黑白的,且和原图像差距很大。如图。 可以看出显示的纹理并不正确。在确保顶点着色器和片段着色器正确的情况下。我按照网上的说法修改了图片大小为128*128保证是2的倍数。这也没有解决问题。接着我......
  • 测试 TIDB in k8s 一次问题记录(pd failed to respond)
    作者:WalterWj问题背景单机装了个k8s,TIDB部署启动的时候看报错好像是tikv访问不到pd。但是通过tikvpodcurlpd端口是通的(404代表通,但是API不存在)。而且可以看到本地目录pd是预期产生文件了的。登录pdpod,执行pd-ctl也是正常执行。member命令下有内容解决方法......
  • 通过 Windows 安全中心添加排除项:
    在Windows中设置排除项,即将特定的文件、文件夹或进程从WindowsDefender的扫描中排除,可以通过Windows安全中心来完成。以下是详细步骤:通过Windows安全中心添加排除项:打开Windows设置:按下Windows键+I,打开设置。进入更新与安全:在设置菜单中,选择“更新与安全......
  • 吉利汽车:S-SDLC融入开发体系,推动智能汽车安全发展
    吉利汽车是中国汽车行业的知名品牌,是一家具有国际化视野的汽车企业,在中国汽车市场自主品牌中占据领军地位。吉利汽车集团数字化中心利用数字化技术优势赋能业务升级,推动研发效率提升和产品安全能力拓展,进行整体数字化转型。在数字化转型过程,为了应对软件开发面临的种种风险,吉利汽车......
  • 解决“yarn : 无法加载文件 C:\Users\quber\AppData\Roaming\npm\yarn.ps1,因为
    1、......
  • 深入浅出Java多线程(二):Java多线程类和接口
    引言大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第二篇内容:Java多线程类和接口。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!!在现代计算机系统中,多线程技术是提升程序性能、优化资源利用和实现并发处理的重要手段。特别是在Java编程语言中,多线程机......
  • js在fori循环中根据索引删除元素时,应该注意哪些问题
    在for循环中根据索引删除数组元素时,应当特别注意以下问题:直接修改循环变量:在JavaScript或其他一些语言中,如果你直接使用for循环遍历数组并删除当前迭代的元素,将会导致索引错乱。因为当你删除一个元素后,数组的长度会减小,但循环的索引并不会因此自动调整。索引越界:......
  • 解决使用PowerShell执行命令出现“因为在此系统上禁止运行脚本”的问题
    1、......
  • k8s集群断电后 机器不能启动问题--- Centos 服务器 无法启动 Failed to start Login S
    参考文档:https://blog.csdn.net/hedao0515/article/details/129718094先说下主要原因,是因为断电后有些文件没有完整写入,导致文件系统错误,需要借助原生工具修复文件系统。重启机器,进入linux选择内核页面,按ctrl+x进入引导页面,在linux16这一行最后填上init=/bin/bash有可能......