• 35.认识线程安全【20220813】


    先看看这个卖票实例:

    1. package com.wangxing.test6;
    2. public class MyThread1 implements Runnable{
    3. private int piao=5;
    4. @Override
    5. public void run() {
    6. //得到线程名称
    7. String name=Thread.currentThread().getName();
    8. //持续买票
    9. boolean flag=true;
    10. while(flag){
    11. //判断有没有可卖的票
    12. if(piao>0){
    13. //收钱--找钱--打印票
    14. try {
    15. Thread.sleep(2000);
    16. } catch (InterruptedException e) {
    17. e.printStackTrace();
    18. }
    19. System.out.println(name+",卖出1张票,还剩"+(--piao)+"张");
    20. }else{
    21. flag=false;
    22. }
    23. }
    24. }
    25. }
    26. package com.wangxing.test6;
    27. public class Main1 {
    28. public static void main(String[] args) {
    29. MyThread1 my=new MyThread1();
    30. Thread th1=new Thread(my);
    31. Thread th2=new Thread(my);
    32. Thread th3=new Thread(my);
    33. th1.setName("窗口1");
    34. th2.setName("窗口2");
    35. th3.setName("窗口3");
    36. th1.start();
    37. th2.start();
    38. th3.start();
    39. }
    40. }
    41. 运行结果:
    42. 窗口3,卖出1张票,还剩3
    43. 窗口1,卖出1张票,还剩4
    44. 窗口2,卖出1张票,还剩2
    45. 窗口2,卖出1张票,还剩1
    46. 窗口1,卖出1张票,还剩0
    47. 窗口3,卖出1张票,还剩-1

    分析结果:当窗口1卖最后一张票的时候,在收钱打印票的时候,还没有来得及对票数进行减1之前,线程就切换给了窗口3,窗口3认为还有一张票,窗口3就收钱收钱打印票的时候,还没有来得及对票数进行减1之前,线程有切换给了窗口1,窗口1就对票数进减1,完成以后线程切换给窗口3,窗口3对对票数进行减1此时就得到-1这个值。

    经过上面运行程序的分析,我得到的结果是:当多条线程,同时访问同一个资源的时候,会产生数据不一致的错误情况。

    为了解决这种数据不一致的错误情况,我们才学习线程同步。

    什么是线程同步/线程安全?

    线程同步也叫线程安全,当多条线程,同时访问同一个资源的时候,每一次只能由多条线程中的其中一条访问公共资源,当这一条线程访问公共资源的时候,其他的线程都处于等待状态,不能访问公共资源,当这一条线程访问完了公共资源以后,其他线程中的一条线程才能访问资源,剩下的线程继续等待,等待当前线程访问结束,实现这个过程就是线程同步。【排队访问资源】

    线程同步/线程安全的实现方式

    1.Synchronized关键字 【同步代码块/同步方法】
       1.1.同步代码块
             格式:synchronized(同步对象){

             }

    1. package com.wangxing.test6;
    2. public class MyThread2 implements Runnable {
    3. private int piao = 5;
    4. @Override
    5. public void run() {
    6. // 得到线程名称
    7. String name = Thread.currentThread().getName();
    8. // 持续买票
    9. boolean flag = true;
    10. while (flag) {
    11. // 同步代码块
    12. synchronized (this) {
    13. // 判断有没有可卖的票
    14. if (piao > 0) {
    15. // 收钱--找钱--打印票
    16. try {
    17. Thread.sleep(2000);
    18. } catch (InterruptedException e) {
    19. e.printStackTrace();
    20. }
    21. System.out.println(name + ",卖出1张票,还剩" + (--piao) + "张");
    22. } else {
    23. flag = false;
    24. }
    25. }
    26. }
    27. }
    28. }

    同步代码块虽然可以实现买票的效果,但是它在使用的时候,需要设置一个同步对象,由于我们很多时候都不知道这个同步对象应该是谁,容易写错,造成死锁的情况。正是应为这个缺点,我们很少使用同步代码块来实现线程同步。

     1.2.同步方法
           同步方法的定义格式: 访问限制修饰符  synchronized  方法返回值类型 方法名称(){}

    1. package com.wangxing.test6;
    2. public class MyThread3 implements Runnable{
    3. private int piao=5;
    4. //持续买票
    5. boolean flag=true;
    6. @Override
    7. public void run() {
    8. //得到线程名称
    9. String name=Thread.currentThread().getName();
    10. while(flag){
    11. sellpiao(name);
    12. }
    13. }
    14. //同步方法
    15. public synchronized void sellpiao(String name){
    16. //判断有没有可卖的票
    17. if(piao>0){
    18. //收钱--找钱--打印票
    19. try {
    20. Thread.sleep(500);
    21. } catch (InterruptedException e) {
    22. e.printStackTrace();
    23. }
    24. System.out.println(name+",卖出1张票,还剩"+(--piao)+"张");
    25. }else{
    26. flag=false;
    27. }
    28. }
    29. }

    2.通过Lock接口
       public interface Lock
       常用的接口方法
       void    lock() 获得锁。 
       void    unlock() 释放锁。
       由于上面的锁方法是Lock接口,我们要使用就得先创建出Lock接口对象,由于Lock是个接口不能new ,我们就得使用它的子类来创建对象。
       Lock接口得子类ReentrantLock

    1. package com.wangxing.test6;
    2. import java.util.concurrent.locks.Lock;
    3. import java.util.concurrent.locks.ReentrantLock;
    4. public class MyThread4 implements Runnable{
    5. private int piao=5;
    6. //定义Lock对象
    7. private Lock mylock=new ReentrantLock();
    8. @Override
    9. public void run() {
    10. //得到线程名称
    11. String name=Thread.currentThread().getName();
    12. //持续买票
    13. boolean flag=true;
    14. while(flag){
    15. //void lock() 获得锁。
    16. mylock.lock();
    17. //判断有没有可卖的票
    18. if(piao>0){
    19. //收钱--找钱--打印票
    20. try {
    21. Thread.sleep(500);
    22. } catch (InterruptedException e) {
    23. e.printStackTrace();
    24. }
    25. System.out.println(name+",卖出1张票,还剩"+(--piao)+"张");
    26. }else{
    27. flag=false;
    28. }
    29. //void unlock() 释放锁。
    30. mylock.unlock();
    31. }
    32. }
    33. }

    Synchronized关键字与Lock接口的区别?
    synchronized:
        1.synchronized关键字
        2.自动锁定资源,不灵活
        3.异常时会自动释放锁
        4.不能中断锁,必须等待线程执行完成释放锁。
    Lock:
        1.Lock接口
        2.手动锁定资源,灵活
        3.异常时不会自动释放锁,所以需要在finally中实现释放锁
        4.可以中断锁

  • 相关阅读:
    一键导入文件夹下全部文件,按文件名快速分类保存
    边端融合系统安全风险分析及评估方法
    vue 可视化大屏适配插件之过程篇
    CopyOnWriteArrayList源码分析
    九洲
    vue自定义全局指令v-emoji限制input输入表情和特殊字符
    设计模式之访问者模式
    JsonCpp JSON格式处理库的介绍和使用(面向业务编程-文件格式处理)
    java-net-php-python-net本科生毕业设计选导师系统演示录像2019计算机毕业设计程序
    dubbo3+zookeeper/nacos+dubbo-admin
  • 原文地址:https://blog.csdn.net/guizhaiteng/article/details/126317391