并发安全问题

在互联网企业,一定特别关注并发编程的问题,那什么是并发安全问题呢?并发安全一般在什么情况下会发生呢?
在讨论并发安全问题解决方案之前,先把这问题重现了。


前言

在这里插入图片描述

看上面这个图,要产生并发安全问题:有几个事情是应该同时发生的:

  1. 有很多用户(并发场景)
  2. 对同一共享资源进行操作

一、CountDownLatch的使用

CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为初始任务的数量。每当完成了一个任务后,计数器的值就会减1(CountDownLatch.countDown()方法)。当计数器值到达0时,它表示所有的已经完成了任务,然后在闭锁上等待CountDownLatch.await()方法的线程就可以恢复执行任务。

在这里插入图片描述
上图创建了个countDownLatch(5),每一个线程运行后都countDown,既次数-1,当原来的值由5减到0后,5个线程会同时执行orderNumGenerator.getNumber
这样就模拟了一个并发的情况。

二、并发重现

OrderNumGenerator负责生成订单ID,而FileId.nextId的方法是写一个文件,取出文件里面最新的数字,然后+1, 获得结果写入原来的文件,再返回数据(这一步涉及IO的读写操作会占用很多时间,很容易产生并发安全问题)。

package com.zyc.lock;

import com.zyc.utils.FileId;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;

public class OrderNumGenerator {
    
    

    private java.util.concurrent.locks.Lock lock = new ReentrantLock();


    //以lock的方式解决
    public  String getNumber() {
    
    
        try {
    
    
            lock.lock();
            SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
            String s = simpt.format(new Date()) + "-" + FileId.nextId();
            return s;
        }finally {
    
    
            lock.unlock();
        }
    }

}



编写测试方法

package com.zyc.test;

import com.zyc.LockApp;
import cn.zyc.simple.OrderNumGenerator;
import cn.zyc.zk.Lock;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;



@SpringBootTest(classes = LockApp.class)
@RunWith(SpringRunner.class)
public class MySqlTest {
    
    
    private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
    // 使用lock锁
    // private java.util.concurrent.locks.Lock lock = new ReentrantLock();


    @Resource(name = "mysqlLock")
    private Lock lock;



    @Test
    public  void testGetOrderNumber() throws InterruptedException {
    
    
        System.out.println("####生成唯一订单号###");
        for (int i = 0; i < 50; i++) {
    
    
            new Thread(
                    new Runnable() {
    
    
                        public void run() {
    
    
                            getNumber();
                        }
                    }
            ).start();
        }

        Thread.currentThread().join();
    }


    public void getNumber() {
    
    
        try {
    
    
            lock.getLock();
            String number = orderNumGenerator.getNumber();
            System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unLock();
        }
    }
}

模拟50个并发,同时生成订单ID,排序后看输出的结果:
在这里插入图片描述

可以看出最终的结果里面有大量的重复,如果在真实的项目开发中,这会发生很严重的问题

三、问题原因解析

在这里插入图片描述

四、解决方案:使用锁解决

关于锁:

  • 多任务环境
  • 争夺同一资源
  • 对资源的访问互斥

方案一:使用Synchronized解决方案
在这里插入图片描述
在这里插入图片描述
方案二:使用Lock解决方案
在这里插入图片描述
代码实现:

public class OrderNumGenerator {
    
    

    private java.util.concurrent.locks.Lock lock = new ReentrantLock();


    //以lock的方式解决
    public  String getNumber() {
    
    
        try {
    
    
            lock.lock();
            SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
            String s = simpt.format(new Date()) + "-" + FileId.nextId();
            return s;
        }finally {
    
    
            lock.unlock();
        }
    }

}

Synchronized与Lock 区别

Lock接口比同步方法和同步块提供了更具扩展性的锁操作。
他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。
它的优势有:可以使锁更公平,可以使线程在等待锁的时候响应中断,可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间,可以在不同的范围,以不同的顺序获取和释放锁。
整体上来说Lock是synchronized的扩展版,Lock提供了无条件的、可轮询的(tryLock方法)、定时的(tryLock带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition方法)锁操作。另外Lock的实现类基本都支持非公平锁(默认)和公平锁,synchronized只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。

猜你喜欢

转载自blog.csdn.net/weixin_47723535/article/details/109366323