Qing 1 vuosi sitten
vanhempi
commit
d139005091

+ 3 - 0
redis-demo/src/main/java/com/sf/controller/RedisController.java

@@ -1,11 +1,13 @@
 package com.sf.controller;
 
 import com.sf.service.RedisService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
+@Slf4j
 @RestController
 public class RedisController {
 
@@ -16,6 +18,7 @@ public class RedisController {
     @GetMapping("/seckill")
     public String seckill(@RequestParam("userId") String userId,
                           @RequestParam("goodsId") String goodsId) {
+        log.info("userId:{},goodId:{}", userId, goodsId);
         redisService.seckill(userId, goodsId);
         return "success";
     }

+ 59 - 6
redis-demo/src/main/java/com/sf/service/RedisServiceImpl.java

@@ -2,6 +2,9 @@ package com.sf.service;
 
 import com.sf.utils.RedisUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.redis.core.RedisOperations;
+import org.springframework.data.redis.core.SessionCallback;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -14,14 +17,64 @@ public class RedisServiceImpl implements RedisService {
     public void seckill(String userId, String goodsId) {
         // goodsId = 1
         // 1_stock_num
-        String key = goodsId + "_stock_num";
-        Integer num = (Integer) redisUtils.get(key);
-        if (num > 0) {
-            Long decr = redisUtils.decr(key);
-            System.out.println(decr);
-        }
+//        String key = goodsId + "_stock_num";
+//        Integer num = (Integer) redisUtils.get(key);
+//        // 出现超卖的原因
+//        // 当10个线程同时获取num的值  此时num=5 会同时进行自减操作
+//        if (num > 0) {
+//            Long decr = redisUtils.decr(key);
+//            System.out.println(decr);
+//        }
+        handleByTran(userId, goodsId);
+
+        // 加锁  乐观锁和悲观锁
+        //  以悲观的态度看待  如果修改数据时不增加锁  一定会出现问题
+        //  以乐观的态度看待  如果修改数据时不增加锁  不一定会出现问题
+
+        //  乐观锁
+        //  id=1  name=zhangsan  -> 修改为lisi
+        //        version=1
+        //                       -> 修改为wangwu
+        //    第一个请求  记录数据的值和数据的版本   name=zhangsan version=1  newName=lisi
+        //    第二个请求  记录数据的值和数据的版本   name=zhangsan version=1  newName=wangwu
+
+        //  id=1  name=lisi version=2
+        //    第二个请求  记录数据的值和数据的版本   name=zhangsan version=1  newName=wangwu
+        //  如果请求传递的数据和版本与当前数据版本不匹配  不予处理 打回
+        //  第二个请求  记录数据的值和数据的版本   name=lisi version=2  newName=wangwu
+
+        //  第三个请求  记录数据的值和数据的版本   name=lisi version=2  newName=laoliu
+        //  id=1  name=wangwu version=3
+
+
+        // 悲观锁  性能低但安全性高
+        // mysql中的行锁 就是悲观锁
+
     }
 
+    // 尝试通过事务处理
+    public void handleByTran(String userId, String goodsId) {
+        // 编写事务处理的逻辑
+        SessionCallback sessionCallback = new SessionCallback() {
+            @Override
+            public Object execute(RedisOperations operations) throws DataAccessException {
+                // watch - multi - command - exec
+                String key = goodsId + "_stock_num";
+                operations.watch(key);
+                Integer num = (Integer) redisUtils.get(key);
+                if (num <= 0) {
+//                    System.out.println();
+                    return "已经被秒杀一空";
+                }
+                operations.multi();
+                redisUtils.decr(key);
+                // 代表一笔订单生成  订单id=商品id+用户id
+                redisUtils.set(goodsId + "_" + userId, "1");
+                return operations.exec();
+            }
+        };
+        redisUtils.execute(sessionCallback);
+    }
 
     @Override
     public void init() {

+ 10 - 3
redis-demo/src/main/java/com/sf/utils/HttpUtils.java

@@ -8,9 +8,10 @@ import java.time.Duration;
 
 public class HttpUtils {
 
-    public static void main(String[] args) throws Exception {
+    // 模拟使用20个线程发送url请求
+    public static String seckill(String url) throws Exception {
 //        String url = "http://localhost:8080/init";
-        String url = "http://localhost:8080/seckill?userId=111&goodsId=1";
+//        String url = "http://localhost:8080/seckill?userId=111&goodsId=1";
         // Get请求
         // 可以设置http的版本  也可以设置请求的超时时间
         //  http客户端 类似浏览器一样
@@ -25,6 +26,12 @@ public class HttpUtils {
         HttpResponse<String> httpResponse = httpClient.send(
                 httpRequest, HttpResponse.BodyHandlers.ofString());
         String body = httpResponse.body();
-        System.out.println(body);
+//        System.out.println(body);
+        return body;
+    }
+
+    public static void main(String[] args) throws Exception {
+        String url = "http://localhost:8080/init";
+        seckill(url);
     }
 }

+ 9 - 0
redis-demo/src/main/java/com/sf/utils/RedisUtils.java

@@ -2,6 +2,7 @@ package com.sf.utils;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.SessionCallback;
 import org.springframework.stereotype.Component;
 
 import java.util.Map;
@@ -35,4 +36,12 @@ public class RedisUtils {
     public void setAllMap(String key, Map<String, Object> map) {
         redisTemplate.opsForHash().putAll(key, map);
     }
+
+    public Object execute(SessionCallback sessionCallback) {
+        return redisTemplate.execute(sessionCallback);
+    }
+
+    public boolean hasKey(String key) {
+        return redisTemplate.hasKey(key);
+    }
 }

+ 19 - 0
redis-demo/src/main/java/com/sf/utils/thread/HttpCallable.java

@@ -0,0 +1,19 @@
+package com.sf.utils.thread;
+
+import com.sf.utils.HttpUtils;
+
+import java.util.concurrent.Callable;
+
+public class HttpCallable implements Callable<String> {
+    private String url;
+
+    public HttpCallable(String url) {
+        this.url = url;
+    }
+
+    @Override
+    public String call() throws Exception {
+//        String url = "http://localhost:8080/seckill?userId=111&goodsId=1";
+        return HttpUtils.seckill(url);
+    }
+}

+ 34 - 0
redis-demo/src/main/java/com/sf/utils/thread/TestCallable.java

@@ -0,0 +1,34 @@
+package com.sf.utils.thread;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+public class TestCallable {
+
+    public static void main(String[] args)
+            throws InterruptedException, ExecutionException {
+
+        String url = "http://localhost:8080/seckill?userId=111&goodsId=1";
+
+        Thread[] threads = new Thread[20];
+        FutureTask[] futureTasks = new FutureTask[20];
+        for (int i = 0; i < threads.length; i++) {
+
+            HttpCallable httpCallable = new HttpCallable(url);
+            //call()只是线程任务,对线程任务进行封装
+            // FutureTask是Future的实现类
+            FutureTask<String> task = new FutureTask<>(httpCallable);
+            futureTasks[i] = task;
+
+            Thread thread = new Thread(task);
+            threads[i] = thread;
+            threads[i].start();
+        }
+
+        for (int i = 0; i < futureTasks.length; i++) {
+            FutureTask futureTask = futureTasks[i];
+            System.out.println(futureTask.get());
+        }
+
+    }
+}

+ 29 - 0
redis-demo/src/main/java/com/sf/utils/thread/TestRunnable.java

@@ -0,0 +1,29 @@
+package com.sf.utils.thread;
+
+import com.sf.utils.HttpUtils;
+
+public class TestRunnable {
+
+    public static void main(String[] args) {
+        String url = "http://localhost:8080/seckill?userId=111&goodsId=1";
+        Thread[] threads = new Thread[20];
+        for (int i = 0; i < threads.length; i++) {
+            Thread thread = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        HttpUtils.seckill(url);
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            });
+            threads[i] = thread;
+            threads[i].start();
+        }
+
+//        for (int i = 0; i < threads.length; i++) {
+//            threads[i].start();
+//        }
+    }
+}

+ 44 - 0
redis-demo/src/main/java/com/sf/utils/thread/TestThreadPool.java

@@ -0,0 +1,44 @@
+package com.sf.utils.thread;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.*;
+
+public class TestThreadPool {
+
+    public static void main(String[] args) {
+        // 使用线程池来创建多线程  需要指定初始化参数
+        // new ThreadPoolExecutor()
+        // Executors 使用线程池的工具类
+        // 可以通过newFixedThreadPool 获取固定个数的线程池
+        ExecutorService executorService = Executors.newFixedThreadPool(20);
+
+        // Runnable Callable
+        // 对于Callable  要通过Future/FutureTask 来获取返回结果
+
+        String url = "http://localhost:8080/seckill?";
+        List<Callable<String>> tasks = new ArrayList<>();
+        for (int i = 0; i < 20; i++) {
+            // 动态url的处理
+            String newUrl = url + "userId=" + i + "&goodsId=1";
+            System.out.println(newUrl);
+            tasks.add(new HttpCallable(newUrl));
+        }
+
+        try {
+            // 把准备好的线程 扔进线程池 并且触发执行
+            List<Future<String>> results = executorService.invokeAll(tasks);
+            for (Future<String> result : results) {
+                System.out.println(result.get());
+            }
+        } catch (InterruptedException | ExecutionException e) {
+            throw new RuntimeException(e);
+        }
+//        catch (ExecutionException e) {
+//            throw new RuntimeException(e);
+//        }
+
+    }
+}
+
+