• [技术干货] 玩转MybatisPlus分页插件二:自定义分页函数[简单案例+复杂案例]+Demo实践常规处理操作
    前言 这种场景非常常见,就是当前的查询语句是我们自定义的,想要在自己写的sql语句中通过分页插件来实现分页功能(比如说我需要在教师表中查询name不为空的⽤户,并且职称为讲师,年龄小于40的教师对象并将查询结果分页等等…) 还没有看过上一篇的朋友,这里是传送门:玩转MyBatis-Plus分页插件一:基本使用 如果对MybatisPlus分页插件基本使用已经非常熟练的朋友就接着往下看吧  开整 玩自定义分页必须掌握的神器就是:条件构造器wrapper、Lambda条件构造器如果还没有学会的朋友可以去官网先学习一波 前面说到,分页它的本质就是内部封装了一个拦截器,对于满足满足条件的数据,起到一个过滤的作用这会和我们后续的自定义条件有关,毕竟也是同样的道理,你自定义一个条件来“筛选数据”,只展示满足条件的数据 上一篇博文我们在mapper的selectPage函数当中将wrapper设为null,则是查询全部数据。 自定义分页的核心就是:参照selectPage函数 自定义分页的重点就是:自定义函数的返回值必须为Page对象,你所定义的函数的第一个参数类型必须为Page  简单案例[体验一波] 目标数据表t_product  SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;  -- ---------------------------- -- Table structure for t_product -- ---------------------------- DROP TABLE IF EXISTS `t_product`; CREATE TABLE `t_product`  (   `id` int(0) NULL DEFAULT NULL,   `name` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,   `price` decimal(10, 0) NULL DEFAULT NULL,   `stock` int(0) NULL DEFAULT NULL,   `proPic` varchar(765) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,   `isHot` bit(1) NULL DEFAULT NULL,   `isSwiper` bit(1) NULL DEFAULT NULL,   `swiperPic` varchar(765) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '轮转图片的地址',   `swiperSort` int(0) NULL DEFAULT NULL COMMENT '排序',   `typeId` int(0) NULL DEFAULT NULL COMMENT '类别id',   `hotDateTime` datetime(0) NULL DEFAULT NULL COMMENT '设置热卖的时间',   `productIntroImgs` varchar(600) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,   `productParaImgs` varchar(600) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,   `description` varchar(600) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品描述' ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;  -- ---------------------------- -- Records of t_product -- ---------------------------- INSERT INTO `t_product` VALUES (1, '小米电视大师 82英寸', 10999, 3451, '21.png', b'0', b'1', '2.jpg', 3, 14, '2022-04-26 21:36:34', '\"\"', '\"\"', '\"\"'); INSERT INTO `t_product` VALUES (4, 'Xiaomi 11', 3799, 3232, '6.png', b'1', b'0', 'default.jpg', 0, 2, '2022-05-31 21:36:34', '<img width=\'100%\' src=\'http://localhost:8001/image/productIntroImgs/11.jpg\'></img>', '<img width=\'100%\' src=\'http://localhost:8001/image/productParaImgs/11.jpg\'></img>', '「全版本领券立减200元,券后价3299元起;享至高24期免息;赠手机保护壳*1;【全款支付套装】赠果冻包」'); INSERT INTO `t_product` VALUES (5, 'Redmi K40 游戏增强版', 2299, 2005, '11.png', b'0', b'1', '1.jpg', 1, 39, NULL, '\"\"', '\"\"', '\"\"'); INSERT INTO `t_product` VALUES (6, 'Xiaomi 11 Pro', 4499, 2343, '1.png', b'1', b'0', 'default.jpg', 0, 2, '2021-09-12 21:36:34', '\"\"', NULL, NULL); INSERT INTO `t_product` VALUES (7, 'Xiaomi MIX FOLD折叠屏手机', 7999, 2222, '2.png', b'1', b'0', 'default.jpg', 0, 3, '2021-10-28 21:36:34', '\"\"', NULL, NULL); INSERT INTO `t_product` VALUES (8, 'Note 9 5G', 1199, 1111, '3.png', b'1', b'0', 'default.jpg', 0, 40, '2022-02-23 21:36:34', '\"\"', NULL, NULL); INSERT INTO `t_product` VALUES (9, 'Xiaomi 10S', 2999, 1111, '4.png', b'1', b'0', 'default.jpg', 0, 2, '2021-12-28 21:36:34', '\"\"', NULL, NULL); INSERT INTO `t_product` VALUES (10, 'Note 9 Pro 5G', 1399, 2222, '5.png', b'1', b'0', 'default.jpg', 0, 40, '2021-11-28 21:36:34', '\"\"', NULL, NULL); INSERT INTO `t_product` VALUES (11, '黑鲨4', 2499, 3322, '7.png', b'1', b'0', 'default.jpg', 0, 41, '2021-11-28 21:36:34', '\"\"', NULL, NULL); INSERT INTO `t_product` VALUES (12, 'Redmi  K40 Pro 系列', 2499, 3244, '8.png', b'1', b'0', 'default.jpg', 0, 39, '2021-11-28 21:36:34', NULL, NULL, NULL); INSERT INTO `t_product` VALUES (14, 'Xiaomi Civi', 2599, 4444, '9.png', b'0', b'0', 'default.jpg', 0, 1, NULL, '<img width=\'100%\' src=\'http://localhost:8001/image/productIntroImgs/1.jpg\'></img><img width=\'100%\' src=\'http://localhost:8001/image/productIntroImgs/2.jpg\'></img><img width=\'100%\' src=\'http://localhost:8001/image/productIntroImgs/3.jpg\'></img>', '<img width=\'100%\' src=\'http://localhost:8001/image/productParaImgs/1.jpg\'></img><img width=\'100%\' src=\'http://localhost:8001/image/productParaImgs/2.jpg\'></img>', '「购机至高享24期免息;赠Redmi AirDots 2真无线蓝牙耳机;赠Keep会员7天体验卡;+110元得Air2 SE蓝牙耳机」'); INSERT INTO `t_product` VALUES (15, 'Xiaomi 11 Ultra', 5499, 4444, '10.png', b'0', b'0', 'default.jpg', 0, 2, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (16, '小米平板5', 1999, 444, '13.png', b'0', b'0', 'default.jpg', 0, 5, NULL, '<img src=\'http://localhost:8001/image/productIntroImgs/111.jpg\'></img><img width=\'100%\' src=\'http://localhost:8001/image/productIntroImgs/222.jpg\'></img><img width=\'100%\' src=\'http://localhost:8001/image/productIntroImgs/333.jpg\'></img>', '<img width=\'100%\' src=\'http://localhost:8001/image/productParaImgs/111.jpg\'></img>', '11英寸大屏 2.5K超清显示 120Hz高刷新率'); INSERT INTO `t_product` VALUES (17, '小米平板5 Pro', 2499, 444, '14.png', b'0', b'0', 'default.jpg', 0, 5, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (19, 'RedmiBook Pro 15 增强版', 5299, 444, '15.png', b'0', b'0', 'default.jpg', 0, 6, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (20, 'Redmi G 2021', 6499, 1999, '16.png', b'0', b'1', '3.jpg', 2, 6, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (21, 'Redmi G 2021 锐龙版', 7499, 2000, '17.png', b'0', b'0', 'default.jpg', 0, 6, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (22, 'RedmiBook Pro 14 增强版', 4999, 777, '18.png', b'0', b'0', 'default.jpg', 0, 6, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (23, '小米笔记本 Pro 14 锐龙版', 5499, 666, '19.png', b'0', b'0', 'default.jpg', 0, 7, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (24, '小米笔记本 Pro 14 增强版', 5499, 666, '20.png', b'0', b'0', 'default.jpg', 0, 7, NULL, NULL, NULL, NULL); INSERT INTO `t_product` VALUES (26, '1', 1, 1, 'default.jpg', b'0', b'0', 'default.jpg', 0, 6, NULL, '1', '1', NULL); INSERT INTO `t_product` VALUES (27, '1', 2, 3, 'default.jpg', b'0', b'0', 'default.jpg', 0, 6, NULL, '5', '6', '4'); INSERT INTO `t_product` VALUES (28, '2', 3, 4, 'default.jpg', b'0', b'0', 'default.jpg', 0, 6, NULL, '6', '7', '5'); INSERT INTO `t_product` VALUES (29, '1', 1, 1, 'default.jpg', b'0', b'0', 'default.jpg', 0, 11, NULL, '1', '1', '1'); INSERT INTO `t_product` VALUES (30, '223335552', 322, 22355, 'default.jpg', b'0', b'0', 'default.jpg', 0, 6, NULL, '22333522', '22333544442', '22333511');  SET FOREIGN_KEY_CHECKS = 1; 例如:想要查询出价格大于某个值的产品并进行分页(这里为了测试sql定义了该值为1400) 1.写出sql语句 共查询到18条数据 2.自定义分页函数 @Select("SELECT * FROM `t_product` where price>#{price}") Page<Product> selectPageByMyself(@Param("page") Page<Product> page, @Param("price") Integer price); 3.测试 @SpringBootTest public class MybatisPageTest {     @Autowired     public ProductMapper mapper;          @Test     public void test(){         Page<Product> page = new Page<>(1,10);         mapper.selectPageByMyself(page,1400);         System.out.println(page);     } } 我这里设置了每页10条数据 可以看到将第一页的数据全部查询出来了,修改参数即可查看第二页的数据 复杂 可以看到我这里只是简单的对price进行了筛选,如果说我现在的查询条件变得很复杂了,该怎么处理呢?这里要使用到的就是我们一直提到的条件构造器来解决这个问题 比如,我想要查询商品名称模糊查询,价格小于[前端传过来的值],isHot参数为[前端传过来的值]的商品有哪些,并进行分页 常规操作 一般遇到这种指定条件的分页情况,都是通过前端传过来一个vo对象,然后根据这个vo对象进行操作解析,简单说明就是:我们不需要对product类全部字段进行操作,只需要几个重要字段就行了 以下是使用了thymeleaf来做测试  1.创建vo对象[需要什么就添加什么,和实体类名称类型保持一致import lombok.Data;  /**  * @description: product vo对象类  * @author: xmonster_大魔王  * @create: 2022-07-28 20:00  **/  @Data @Builder @AllArgsConstructor @NoArgsConstructor public class ProductParam {      private String name;     private int price;     private Boolean ishot;  }  2.编写mapper层 /**  * <p>  *  Mapper 接口  * </p>  *  * @author xmonster_大魔王  * @since 2022-06-16  */ @Mapper public interface ProductMapper extends BaseMapper<Product> {      @Select("SELECT * FROM `t_product` where price>#{price}")     Page<Product> selectPageByMyself(@Param("page") Page<Product> page, @Param("price") Integer price);      @Select("SELECT * FROM `t_product` ${ew.customSqlSegment}")     Page<Product> selectPageByMyself2(@Param("page") Page<Product> page,@Param(Constants.WRAPPER) Wrapper<Product> wrapper); } 这里主要看selectPageByMyself2方法啦,直接传wrapper即可,这里的sql语句当中使用了 ${ew.customSqlSegment},那么它具体的用法是什么呢?  ${ew.customSqlSegment} 拼接where后的语句(包括where,需注意在动态SQL中不处于标签内) 我们来一起看看我们的SQL语句就明白啦  3.编写controller[重点] 这里可以看到,我们编写了一个ProductParam的对象,并对这个对象填入我们需要查询的信息,这里我要查询商品名称中包含“x”,价格小于8000,并且ishot属性为true  @GetMapping("") public String index(Model model){     ProductParam param = new ProductParam().builder().name("x").price(8000).ishot(true).build();     Page<Product> page = new Page<>(1,10);     LambdaQueryWrapper<Product> wrapper = Wrappers.lambdaQuery(Product.class)             .like(Product::getName, param.getName())             .lt(Product::getPrice, param.getPrice())             .eq(Product::getIshot, param.getIshot());     mapper.selectPageByMyself2(page,wrapper);     model.addAttribute("productList",page.getRecords());     return "index"; } 4.前端展示 <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head>     <meta charset="UTF-8">     <title>product分页测试</title>     <link rel="stylesheet" th:href="@{/asserts/bootstrap5/css/bootstrap.min.css}" />  <!--    <link rel="stylesheet" th:href="@{/message/message.css}"/>--> </head> <body> <!--页面展示--> <table class="table table-dark table-hover" style="margin:10px auto;width: 800px">     <thead>         <th>name</th>         <th>price</th>         <th>stock</th>         <th>isHot</th>         <th>typeId</th>     </thead>     <tbody>        <tr th:each="product: ${productList}">             <td ><th:block th:text="${product.name}"></th:block></td>             <td ><th:block th:text="${product.price}"></th:block></td>             <td ><th:block th:text="${product.stock}"></th:block></td>             <td ><th:block th:text="${product.ishot}"></th:block></td>             <td ><th:block th:text="${product.typeid}"></th:block></td>         </tr>     </tbody>  </table> <script th:src="@{/asserts/bootstrap5/js/bootstrap.bundle.min.js}"></script> <script th:src="@{/asserts/jquery/jquery.min.js}"></script> </body> </html>  5.效果 看到sql语句后面自动添加了where条件进行筛选,具体和${ew.customSqlSegment}有关的其他信息可以去查查资料深入一下它可是非常常用的 好啦,这就是自定义分页函数基础的内容,体验了一波之后我相信更多的朋友想要的效果当然是和前端进行交互的那种真正感受分页的快乐,这里的操作都是在后台进行输出,前台只是简单的展示,与前端进行交互的内容我会放在下一期~也会介绍很多好看的分页效果 ———————————————— 原文链接:https://blog.csdn.net/Xmumu_/article/details/126032264 
  • [技术干货] 《Java并发编程实战》读书笔记三:使用Executor框架
    一、Executor框架选择串行的方式执行任务,串行处理机制通常无法提高高吞吐率和快速响应性,于是我们可以显式地为任务创建线程,为每一个请求创建一个线程来执行任务,这样可以实现更高的响应性。但是这样会带来很多问题:1. 线程的创建和销毁开销非常高2. 活跃的线程会消耗系统资源,尤其是内存。如果可运行的线程数量多于可用处理器的数量,那么有些线程就会闲置。大量空闲的线程会占用许多内存,给GC带来很大的压力,而且大量线程在竞争CPU资源时还会产生其他性能开销。3. 可创建线程的数量存在一个限制。这个限制值受多个平台制约,包括JVM的启动参数,Thread构造函数中请求栈的大小以及操作系统的限制。总得来说,增加线程可以提高系统的吞吐率但是如果超出了这个范围,再创建更多的线程只会降低程序的速度,更严重会导致奔溃。1.使用Executor框架对于线程和任务,任务是一组逻辑工作单元,而线程是使任务异步执行的机制,Executor框架可以将线程和任务协调起来。Executor基于生产者—消费者模型,它提供了一个标准的方法,将任务的提交和执行过程解耦,用Runnable来表示任务。提交任务的操作相当于生产者,执行任务的线程相当于消费者。Executor有两种实现方式:每线程每任务public class ThreadPerTaskExecutor implements Executor { public void execute(Runnable command) { new Thread(command).start(); } } 一个线程所有任务:public class WithinThreadExecutor implements Executor { public void execute(Runnable command) { command.run(); } } 每当你看到new Thread(runnable).start();且你希望获得一种更灵活的执行策略时,考虑使用Executor代替Thread2.线程池线程池的优势:1. 通过重用现有的线程而不是创建新线程,可以减少创建和销毁线程的开销2. 当请求到来时,由于线程已经存在,可以减少等待时间,从而提高了响应性可以通过调用Executors中的静态工厂方法之一来创建一个线程池:newFixedThreadPool创建一个定长的线程池, 每当提交一个任务就创建一个线程, 直到达到池的最大长度, 这时线程池会保持长度不再变化. (一任务一线程)20170118125021499.pngnewCachedThreadPool 创建一个可缓存的线程池, 如果当前线程的长度超过了处理的需要时, 它可以灵活的回收空闲的线程, 当需求增加时, 它可以灵活的增加新的线程, 而并不会对池的长度做任何限制. (缓存线程池)20170118125038933.pngnewSingleThreadExecutor 创建一个单线程化的executor, 它只创建唯一的工作者线程来执行任务, 如果这个线程异常结束, 会有另一个取代它, 但是任务会保存在一个queue中等待执行. (多任务一线程)20170118125049125.pngnewScheduleThreadPool 创建一个定长的线程池, 而且支持定时的以及周期性执行任务, 类似timer.(定时线程池)20170118125057656.png可以看见上面的静态方法都几乎是通过ThreadPoolExecutor来实现的参数含义:corePoolSize:池中所保存的线程数,包括空闲线程maximumPoolSize:线程池允许的最大线程数量keepAliveTime:存活时间,当线程数大于核心线程数时,多出来的线程为空余线程,当空余线程在一定的时间内没有新任务到达执行,则终止该线程unit :keepAliveTime 参数的时间单位workQueue :执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务,使用不同的Queue,能够得到不同的功能,例如newFixThreadPool使用的是linkedBolckQueue,而newCachedThreadPool使用的是SynchronizedQueue举例:对于newFixedThreadPool:它实现了一个初始线程数为任务数(nThreads),最大线程数为任务数,等待时间为0(因为单线程单任务,不存在线程等待任务),并使用一个linkedBolckQueue来保存任务的线程池,这就是单任务单线程的实现对于newCachedThreadPool:它实现了一个初始线程数为0,最大线程数为无限大,等待时间为1分钟,并使用一个SynchronizedQueue来保存任务的线程池,所以它是一个无限线程的缓存池3.Executor的生命周期:ExecutorService从上面所述,我们可以看出Executor通常是创建线程来执行任务,为了解决执行服务的生命周期问题,Executor扩展了ExecutorService接口,添加了生命周期的管理方法public interface ExecutorService extends Executor { void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; ....ExecutorService有三种状态: running(运行), shuting down(关闭), terminated(已终止)。shuting down(关闭)状态:shutdown:将停止接受新的任务, 同时等待已经提交的任务完成, 包括尚未完成的任务showdownNow:会启动一个强制的关闭过程, 尝试取消所有运行中的任务和排在队列中尚未开始的任务,并把排队中尚未开始的任务返回。对于关闭后提交到ExecutorService中的任务, 会被(拒接执行处理器)rejected execution handler处理,它会抛弃任务,或者使得execute方法抛出一个未检查的RejectedExecutionException。terminated(已终止)状态:等所有任务都完成之后,进入terminated状态, 可以调用awaitTermination等待ExecutorService到达终止状态, 也可以轮询检查isTerminated判断是否终止. 通常shutdown会紧随awaitTermination之后, 这样可以产生同步地关闭ExecutorService的效果.4.延迟任务和周期任务延迟任务:在100ms后执行任务周期任务:没100ms执行一次任务一把来说,Timer类用于执行延迟任务和周期任务,但是Timer有以下两个问题:1. 只会创建一个线程来执行所有task, 如果一个task非常耗时, 会导致其他的task的实效准确性出问题2. Timer线程并不捕获异常,对于一些未检查异常(RuntimeException)抛出,Timer线程会被终止public class Test { public static void main(String[] args) throws InterruptedException{ Timer timer = new Timer(); timer.schedule(new myTask(), 1);//这里抛出RuntimeException之后,整个Timer线程会被终止 Thread.sleep(1000); timer.schedule(new myTask(), 1); Thread.sleep(5000); } static class myTask extends TimerTask{ @Override public void run() { throw new RuntimeException(); } }}因此我们一般不使用Timer,可以使用DelayQueue来实现自己的调度服务, 它使BlockingQueue的一种实现,并为ScheduledThreadPoolExecutor提供了调度功能。其内部包含了一个Delayed对象的容器. Delayed是一种延迟时间对象, 只有元素过期后,它才会让你执行take获取元素(这个元素实现了Delayed接口)。5.Callable对比Runnablepublic class ThreadPerTaskExecutor implements Executor { public void execute(Runnable command) { new Thread(command).start(); } }Exceutor框架使用Runnable作为最基本的任务形式,但是Runnable有一种很大的局限性,它不能返回一个值或抛出一个受检查的异常。Callable是一种更好的任务形式,它能返回一个值或者抛出一个异常public interface Callable<V> { V call() throws Exception;}Executor执行任务时有4个生命周期阶段:创建,提交,开始,完成。对于已提交但尚未开始的任务,调用shutdownNow()可以取消并返回这些任务;对于已经开始的任务,只有当他们能响应中断时,才能取消。6.Callable和Future结合使用返回结果public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;get()方法的行为将取决于任务的状态,如果任务已经完成,那么get会立即返回结果或者抛出一个Exception,如果任务没有完成,那么get将阻塞并直到任务完成。将一个Runnable或者Callable任务传递给ExecutorService的submit方法,将返回一个Future用于获得任务的执行结果或者取消任务。private final ExecutorService executor = Executors.newCachedThreadPool(); void renderPage(String source) { final List<ImageInfo> imageInfos = scanForImageInfo(source); //下载图片 Callable<List<ImageData>> task = new Callable<List<ImageData>>() { @Override public List<ImageData> call() throws Exception { List<ImageData> list = new ArrayList<ImageData>(); for (ImageInfo imageInfo : imageInfos) { list.add(imageInfo.download()); } } }; //处理图片 Future<List<ImageData>> future = executor.submit(task);//将一个Runnable或者Callable任务传递给ExecutorService的submit方法,将返回一个Future用于获得任务的执行结果或者取消任务。 renderText(source); try { List<ImageData> list = future.get();//get()方法会一直阻塞,直到callable的任务全部完成。 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 由于不需要结果,因此取消任务 future.cancel(true); } catch (ExecutionException e) { e.printStackTrace(); }上面程序还存在一个问题不是很好,就是下载图片和处理图片的速率的协调问题,如果处理图片的速度远远高于下载图片的速度,那跟串行执行的效率可能相差不是很大。7.CompletionService和BlockingQueueCompletionService用来将Executor与BlockingQueue进行结合, 将Callable任务提交给它执行, 然后使用类似队列中的take和poll在结果完整时获得这个结果。public class Test { private final ExecutorService executor = Executors.newCachedThreadPool(); void renderPage(String source) { final List<ImageInfo> imageInfos = scanForImageInfo(source); CompletionService<ImageData> service = new ExecutorCompletionService<ImageData>( executor); for (final ImageInfo imageInfo : imageInfos) { service.submit(new Callable<ImageData>() { public ImageData call() throws Exception { return imageInfo.downloadImage(); } }); } renderText(source); for (int i = 0; i < imageInfos.size(); i++) { Future<ImageData> f; try { f = service.take(); ImageData imageData = f.get(); renderImage(imageData); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }}上面这个改进相对于前一个,在两个方面提高了性能:1. 为每一个图片的下载都创建一个独立任务,把串行下载改为了并行下载,减少了下载图像的总时间2. 通过CompletionService来即时获取每一个下载任务的Future,并通过该Future来获取结果进行处理,有更好的并发性一般来说,我们推荐下面这种用法。二、取消和关闭Java没有提供任何机制来安全地终止线程,但是它提供了中断,这是一种协作机制,能够使一个线程终止另一个线程的当前工作。1.通过取消标志public class PrimeGenerator implements Runnable { private final List<BigInteger> primes = new ArrayList<BigInteger>(); private volatile boolean cancelled; @Override public void run() { BigInteger p = BigInteger.ONE; while (!cancelled) { p = p.nextProbablePrime(); synchronized (this) { primes.add(p); } } } public void cancel() { this.cancelled = true; } public synchronized List<BigInteger> get() { return new ArrayList<BigInteger>(primes); } }客户端执行取消 PrimeGenerator g = new PrimeGenerator(); new Thread(g).start(); try { Thread.sleep(10); } finally { g.cancel(); } 但是这里会有一个问题,这种标志,在生产者消费者模型中,当队列已被填满,生产者put时会被阻塞,如果此时消费者希望取消生产者任务,于是它调用cancel()来改变取消标志,但是生产者无法识别这个标志,因为它已经被阻塞了,它无法从put的阻塞状态恢复过来。2.通过中断机制Thread中的中断方法:public void interrupt();public boolean isInterrupted();public static boolean interrupted();interrupted():会清除当前线程的中断状态,除非你想屏蔽掉这个中断,否则你要对它处理:抛出InterruptedException或者再次调用interrupt恢复中断对中断的理解应该是:它并不会真正中断一个正在运行的线程; 它仅仅只是发送中断请求(这一点很重要)对于上面取消标志的生产者消费者模型,如果使用中断标志就能够解决为。在每一次迭代循环时,有两个位置可以检测出中断:阻塞的put中,循环开始处查询中断状态时,当检测到中断时,我们对它进行处理就可以实现任务的取消了。public class PrimeProducer extends Thread { private final BlockingQueue<BigInteger> queue; public PrimeProducer(BlockingQueue<BigInteger> queue) { this.queue = queue; } @Override public void run() { try { BigInteger p = BigInteger.ONE; while (!Thread.currentThread().isInterrupted()) { queue.put(p.nextProbablePrime()); } } catch (InterruptedException e) { } } public void cancel() { interrupt(); } }3.通过Future来实现取消ExecutorService.submit将返回一个Future来管理任务,Future拥有一个cancle方法boolean cancel(boolean mayInterruptIfRunning);如果mayInterruptIfRunning为true,并且任务当前正在某一个线程运行,那么这个线程能够被中断;如果为false,那么意味着“如果任务还没有启动,则不要运行它”当Future.get抛出InterruptedException或者TimeoutException时,如果你知道不再需要结果,那么就可以调用Future.cancle来取消任务。4.关闭服务如果一个方法需要处理一批任务, 并在所有任务结束前不返回, 那么它可以通过使用私有的Executor来简化服务的生命周期, 其中Executor的寿命限定在该方法中:boolean checkMail(Set<String> hosts, long timeout, TimeUnit unit) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); final AtomicBoolean hasNewMail = new AtomicBoolean(false); // 使用Atmic去掉volatile是因为内部Runnable访问hasNewMail标识, 那么必须是final的, 这样才能避免被修改 try { for (final String host : hosts) { exec.execute(new Runnable() { @Override public void run() { if (checkMail(host)) { hasNewMail.set(true); } } }); } } finally { exec.shutdown(); //关闭 exec.awaitTermination(timeout, unit); //等待结束 } return hasNewMail.get(); }Executor的shutdownNow方法强行关闭一个ExecutorService时, 它试图取消正在进行的任务, 并返回那些已经提交但是尚未开始的任务。然而,对于那些已经开始但尚未结束的任务,我们用常规的方法无法找出,但是可以通过一个容器来保存这些已开始但尚未结束的任务。三、线程池1.线程饥饿死锁在线程池中,如果任务依赖于其他任务,那么可能导致死锁。在单线程的Executor中,第二个任务停留在工作队列中,等待第一个任务的完成,之后到第二个任务开始执行,而第一个任务又需要第二个任务完成的结果,因此这样就导致死锁。只要线程池中的任务需要无限期地等待一些必须由池中其它任务才能提供的资源或条件,例如某个任务等待另一个任务的返回值或执行结果,除非线程池足够大,否则将发生线程饥饿死锁。如果阻塞时间过长,即使不死锁,响应性也会很差。这种情况,我们要限定等待的时间,不要无限的等。大多数平台类库中的阻塞方法, 都同时有限时的和非限时的两个版本, 比如Thread.join, BlockingQueue.put, CountDownLatch.await, Selector.select. 如果出现超等待, 你可以把任务标识为失败, 中止它或者把它重新放回队列, 准备之后执行。2.ThreadPoolExecutor核心池大小, 最大池大小, 存活时间共同管理着线程的创建与销毁. 即使没有任务执行, 池的大小也等于核心池的大小, 并且直到工作队列充满前, 池都不会创建更多的线程. 最大池大小是可以同时活动的线程的上限, 如果一个线程已经闲置的时间超过了存活时间, 它将成为一个被收回的候选者, 如果当前的池的大小超过了核心池的大小, 线程会终止该候选者。workQueue管理这Executor的实现方式总结来说:各种不同类型的线程池,即使通过核心池大小、最大池大小、 存活时间和、实现队列来实现各自类型的功能newFixedThreadPool:工厂为请求的池设置了核心池的大小和最大池的大小, 而且它永远不会超时;newCachedThreadPool:工厂将最大池的大小设置为Integer.MAX_VALUE, 核心池的大小设置为0, 超时时间设置为1分钟, 这样创建出来的无限扩大的线程池会在需求量减少的情况下减少线程数量.ThreadPoolExecutor提供一个BlockingQueue来保存等待执行的任务。基本的排队方法有3种,无界队列,有界队列和同步移交(Synchronous)。newFixedThreadPool和newSingleThreadExecutor:默认使用的是一个无界的LinkedBlockingQueue, 如果所有的工作者线程都处于忙碌状态, 任务将会在队列中等候, 如果任务持续地到达, 超过了它被执行的速度, 队列会无限地增加.SynchronousQueue:当你需要一个庞大,且线程无限的线程池,可以使用SynchronousQueue,它完全绕开队列, 将任务直接从生产者移交到工作者线程, 因为SynchronousQueue并不是一个真正的队列, 而是一种管理直接在线程间移交信息的机制。只有当池的大小是无限的, 或者可以接受任务被拒绝, SynchronousQueue才是一个有实际价值的选择, newCachedThreadPool工厂就是用了SynchronousQueue.3.饱和策略当有界队列被填满后,饱和策略就游泳了。ThreadPoolExecutor的饱和策略可以通过调用setRejectedExecutionHandler来修改。JDK提供了几种不同的实现,每一种实现有不同的饱和策略:AbortPolicy,CallerRunsPolicy,DiscardPolicy和DiscardOldestPolicy。1. 中止策略(AbortPolicy):默认的饱和策略,会抛出RejectedExecutionException: 调用者可以捕获这个隐藏然后编写满足自己需求的处理代码2. 抛弃策略(DiscardPolicy):当最新提交的任务不能进入队列等待执行时, 遗弃(discard)策略会默认放弃这个任务3. 遗弃最旧策略(DiscardOldestPolicy):选择丢弃的任务是本应该接下来就应该执行的任务, 该策略还会尝试去重新提交新任务。(该策略最好不要和优先级队列一起使用)4. 调用者运行策略(CallerRunsPolicy):既不会丢弃哪个任务, 也不会抛出任何异常. 它会把一些任务退回到调用者那里, 从此缓解新任务流. 他不会在池线程中执行最新提交的任务, 但是他会在一个调用了execute的线程中执行。当工作队列充满后, 并没有预置的饱和策略来阻塞execute当工作队列充满后, 并没有预置的饱和策略来阻塞execute. 但是, 使用Semaphore信号量可以实现这个效果public class BoundedExecutor { private final Executor exec; private final Semaphore semahpore; public BoundedExecutor(Executor exec, int bound) { super(); this.exec = exec; this.semahpore = new Semaphore(bound); } public void submitTask(final Runnable command) throws InterruptedException { semahpore.acquire(); //请求资源 try { exec.execute(new Runnable() { @Override public void run() { try { command.run(); } finally { semahpore.release(); } } }); } catch (RejectedExecutionException e) { semahpore.release(); } } } 4.定制ThreadPoolExecutor大多数通过构造函数传给ThreadPoolExecutor的参数(核心池大小, 最大池大小, 活动时间, 拒绝处理器) 都可以在创建之后通过setter进行设置。如果Executors是通过Executors中的某个工厂方法创建(newSingleThreadExecutor除外),那么可以将结果的类型转换为ThreadPoolExecutor以访问设置器(setter):ExecutorService executor = Executors.newCachedThreadPool();if(executor instanceof ThreadPoolExecutor){ ((ThreadPoolExecutor) executor).setCorePoolSize(10);}5.拓展ThreadPoolExecutorThreadPoolExecutor提供了几个可以在子类化中改写的方法(afterExecute, beforeExecute, terminated),这些方法可以用于拓展ThreadPoolExecutor的行为。在执行任务的线程将调用beforeExecute和afterExecute等方法无论任务是正常地从run返回, 还是抛出一个异常, afterExecute都会被调用, 如果抛出一个Error则不会如果任务抛出一个RuntimeException, 任务不会被执行, afterExecute也不会被调用terminated会在线程池完成关闭时调用, 也就是当所有任务已完成并且所有工作者线程也已经关闭后。terminated可以用来释放Executor在生命周期中分配的一些资源, 还可以发出通知, 记录日志或者完成统计信息public class TimingThreadPool extends ThreadPoolExecutor { private final ThreadLocal<Long> startTime = new ThreadLocal<Long>(); private final Logger log = Logger.getLogger("TimingThreadPool"); private final AtomicLong numTask = new AtomicLong(); private final AtomicLong totalTime = new AtomicLong(); public TimingThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } @Override protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); log.fine(String.format("Thread %s: start %s", t, r)); startTime.set(System.nanoTime()); } @Override protected void afterExecute(Runnable r, Throwable t) { try { long endTime = System.nanoTime(); long taskTime = endTime - startTime.get(); totalTime.addAndGet(taskTime); numTask.incrementAndGet(); } finally { super.afterExecute(r, t); } } @Override protected void terminated() { try { log.info(String.format("Terminated: avg time=%dns", totalTime.get() / numTask.get())); }finally { super.terminated(); } } } 6.并行实现递归算法//树节点的串行递归public <T> void sequentialRecursive(List<Node<T>> nodes, Collection<T> results) { for (Node<T> node : nodes) { results.add(node.compute()); sequentialRecursive(node.getChildren(), results); } } //树节点的并行递归public <T> void parallelRecursive(final Executor exec, List<Node<T>> nodes, final Collection<T> results) { for (final Node<T> node : nodes) { exec.execute(new Runnable() { @Override public void run() { results.add(node.compute()); } }); parallelRecursive(exec, nodes, results); } } public <T> Collection<T> getParallelResult(List<Node<T>> nodes) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); Queue<T> resultQueue = new ConcurrentLinkedQueue<T>(); parallelRecursive(exec, nodes, resultQueue); exec.shutdown(); exec.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); return resultQueue; }原文链接:https://www.jianshu.com/p/3ee3f645d347
  • [技术干货] 【Lombok】val 和 var的使用 | 像 JavaScript 一样的去声明变量
    一、简介 1. val val是在lombok 0.10中引入的。 您可以使用val作为局部变量声明的类型,而不是实际编写该类型。执行此操作时,将从初始值设定项表达式推断类型。局部变量也将成为最终变量。此功能仅适用于局部变量和foreach循环,而不适用于字段。初始值设定项表达式是必需的。 val实际上是一种排序的“类型”,在lombok包中作为一个真正的类存在。必须导入它才能使val工作(或使用lombok.val作为类型)。局部变量声明中存在此类型会触发添加final关键字以及复制初始化表达式的类型,从而覆盖“伪”val类型。 警告:此功能目前在NetBeans中不起作用。 2. var var是在lombok 1.16.12中作为实验性功能引入的。 var的工作原理与val完全相同,只是局部变量没有标记为final。 类型仍然完全是从强制初始值设定项表达式派生的,任何进一步的赋值,尽管现在是合法的(因为变量不再是最终的),都不会被用来确定合适的类型。 例如,var x = "Hello"; x = Color.RED; 不起作用;x的类型将被推断为java.lang.String,因此,x = Color.RED的赋值将失败。如果x的类型被推断为java.lang.Object,这段代码就会被编译,但这不是var的工作方式。 3. 区别 var和val的区别在于:val修饰的局部变量没有被标记为final。 var在lombok 1.16.20中被提升为主包;考虑到JEP 286建立的期望,而lombok对var的处理遵循了这些期望,我们决定提升var,尽管这个特性仍然有争议。 Lombok 1.18.22中的新功能:val被替换为final var。 二、示例比较 1. Lombok 写法 import java.util.ArrayList; import java.util.HashMap; import lombok.val;  public class ValExample {   public String example() {     val example = new ArrayList<String>();     example.add("Hello, World!");     val foo = example.get(0);     return foo.toLowerCase();   }      public void example2() {     val map = new HashMap<Integer, String>();     map.put(0, "zero");     map.put(5, "five");     for (val entry : map.entrySet()) {       System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());     }   } } 2. Java 标准写法 import java.util.ArrayList; import java.util.HashMap; import java.util.Map;  public class ValExample {   public String example() {     final ArrayList<String> example = new ArrayList<String>();     example.add("Hello, World!");     final String foo = example.get(0);     return foo.toLowerCase();   }      public void example2() {     final HashMap<Integer, String> map = new HashMap<Integer, String>();     map.put(0, "zero");     map.put(5, "five");     for (final Map.Entry<Integer, String> entry : map.entrySet()) {       System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());     }   } }  三、支持的配置项 lombok.val.flagUsage = [warning | error] (默认: 未设置) 如果配置,Lombok会将val的任何使用标记为warning或error。  lombok.var.flagUsage = [warning | error] (默认: 未设置) 如果配置,Lombok会将var的任何使用标记为warning或error。  四、附属说明 对于复合类型,将推断最常见的超类,而不是任何共享接口。例如bool ? new HashSet() : new ArrayList()是一个具有复合类型的表达式。其结果既是AbstractCollection,也是Serializable。推断的类型将是AbstractCollection,因为它是一个类,而Serializable是一个接口。  在不明确的情况下,例如初始化表达式为 null时,会推断出java.lang.Object。 ———————————————— 原文链接:https://blog.csdn.net/qq_31635851/article/details/121786449 
  • [技术干货] 使用Java Cron表达式实现定时器
    使用Java Cron表达式实现定时器在Java应用程序中,经常需要使用定时器来执行周期性任务。Java提供了许多定时器库,但是Cron表达式是一种非常灵活和强大的方式来实现定时器。在本文中,我们将学习如何使用Java Cron表达式实现定时器。步骤1:添加依赖项首先,我们需要添加quartz依赖项。我们可以在pom.xml文件中添加以下依赖项:<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version></dependency> 步骤2:创建Job类接下来,我们将编写一个Job类来定义要执行的任务。以下是一个示例代码:import org.quartz.Job;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;public class MyJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { // 执行任务 }}在这里,我们实现了Job接口,并覆盖了execute()方法。在execute()方法中,我们可以编写要执行的任务的代码。步骤3:创建Scheduler现在,我们将创建一个Scheduler来调度Job。以下是一个示例代码:import org.quartz.*;import org.quartz.impl.StdSchedulerFactory;public class MyScheduler { public static void main(String[] args) throws SchedulerException { JobDetail job = JobBuilder.newJob(MyJob.class) .withIdentity("myJob", "group1") .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger", "group1") .withSchedule(CronScheduleBuilder.cronSchedule("0 0/5 * * * ?")) .build(); SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); scheduler.start(); scheduler.scheduleJob(job, trigger); }}在这里,我们首先创建了一个JobDetail对象,并将其与MyJob类相关联。我们还为Job指定了一个标识符和一个组。然后,我们创建了一个Trigger对象,并使用Cron表达式来定义触发器的计划。最后,我们创建了一个Scheduler对象,并将Job和Trigger传递给它以安排任务。在这个例子中,我们使用Cron表达式“0 0/5 * * * ?”,它表示每5分钟执行一次任务。您可以使用不同的Cron表达式来定义不同的计划。结论到此为止,我们已经学会了如何使用Java Cron表达式实现定时器。使用Quartz库,我们可以创建高度灵活的定时器,以满足我们的应用程序的需求。
  • [其他] java锁的详解
    锁的种类Java中的锁是一种同步机制,用于保护共享资源,防止多个线程同时访问。在Java中,锁可以分为不同的类型,每种类型都有其特定的优缺点和适用场景。重量级锁(Heavy Lock):重量级锁是一种互斥锁,它提供了最高的线程安全性。当一个线程获得了重量级锁时,其他线程必须等待该线程释放锁才能获得锁。因此,重量级锁的开销较大,可能会导致线程阻塞和性能下降。但是,由于重量级锁提供了最高的线程安全性,所以在需要确保数据完整性和一致性的情况下,重量级锁是非常有用的。轻量级锁(Lightweight Lock):轻量级锁是一种比重量级锁更轻量级的同步机制。它使用CAS操作来实现互斥访问,并且可以在没有竞争的情况下快速获取锁。因此,轻量级锁的开销较小,可以提高并发性能。但是,由于轻量级锁不是互斥的,所以如果多个线程同时尝试获取锁,则可能导致数据不一致的问题。互斥锁(Mutex Lock):互斥锁是一种基本的同步机制,它用于保护共享资源,防止多个线程同时访问。互斥锁提供了原子性操作,即在任何时候只能有一个线程访问共享资源。Java中的synchronized关键字就是一种互斥锁。读写锁(Read-Write Lock):读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。读写锁可以提高并发性能,特别是在读操作比写操作频繁的情况下。Java中的ReentrantReadWriteLock就是一种读写锁。可重入锁(Reentrant Lock):可重入锁允许同一个线程多次获取同一个锁,这意味着线程可以在执行完同步代码后再次获取该锁而不需要重新竞争。可重入锁通常用于递归调用等情况。Java中的ReentrantLock就是一种可重入锁。自旋锁(Spin Lock):自旋锁是一种基于忙等待的同步机制,它在获取锁时会一直循环等待,直到获取到锁为止。自旋锁的开销较大,因为它会占用大量的CPU时间。但是,自旋锁可以提供极高的并发性能,特别是在竞争激烈的情况下。乐观锁(Optimistic Lock):乐观锁是一种不涉及实际锁定的同步机制,它通过版本号或哈希值来检查共享资源是否被其他线程修改过。如果没有修改,则可以更新共享资源。乐观锁可以提高并发性能,但可能会导致数据不一致的问题。Java中的CAS操作就是一种乐观锁实现方式。悲观锁(Pessimistic Lock):悲观锁是一种实际锁定的同步机制,它在访问共享资源之前先将其锁定。悲观锁可以保证数据的一致性,但会导致并发性能下降。Java中的synchronized关键字就是一种悲观锁实现方式。以下是Java中各种锁的实现代码示例:1. synchronized关键字实现互斥锁[悲观锁]: public class SynchronizedDemo { private int count = 0; public synchronized void increment() { count++; } public int getCount() { return count; } } 2. ReentrantLock类实现可重入锁: import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockDemo { private final ReentrantLock lock = new ReentrantLock(); public void doSomething() { lock.lock(); // 可重入锁必须先获取到锁才能执行同步代码,因此需要使用lock()方法获取锁 try { // 同步代码块 } finally { lock.unlock(); // 在finally块中释放锁,即使发生异常也需要释放锁 } } } 3. ReadWriteLock类实现读写锁: import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockDemo { private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public void readData() { rwl.readLock().lock(); // 只允许读取操作获取到读锁,因此需要使用readLock()方法获取读锁 try { // 读取共享资源的操作 } finally { rwl.readLock().unlock(); // 在finally块中释放读锁,即使发生异常也需要释放读锁 } } public void writeData() { rwl.writeLock().lock(); // 只允许写入操作获取到写锁,因此需要使用writeLock()方法获取写锁 try { // 写入共享资源的操作 } finally { rwl.writeLock().unlock(); // 在finally块中释放写锁,即使发生异常也需要释放写锁 } } } 4.自旋锁示例代码,它使用Java中的ReentrantLock类实现:import java.util.concurrent.locks.ReentrantLock; public class SpinLockDemo { private final ReentrantLock lock = new ReentrantLock(); public void doSomething() { while (true) { try { lock.lockInterruptibly(); // 尝试获取锁,如果获取失败则进入循环等待 break; } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 抛出异常时中断当前线程 } } try { // 同步代码块 } finally { lock.unlock(); // 在finally块中释放锁,即使发生异常也需要释放锁 } } } 在上面的示例代码中,doSomething()方法会一直循环等待获取锁。当它尝试获取锁时,如果获取失败,则会抛出InterruptedException异常并中断当前线程。一旦它成功获取到锁,就可以执行同步代码块了。最后,无论同步代码块是否正常执行,doSomething()方法都会在finally块中释放锁。这样可以确保即使发生异常,也不会导致锁没有被正确释放的问题。5. 乐观锁示例代码,它使用Java中的CAS操作实现:import java.util.concurrent.atomic.AtomicInteger; public class OptimisticLockDemo { private final AtomicInteger count = new AtomicInteger(0); public void increment() { int currentCount = count.get(); while (true) { int nextCount = currentCount + 1; if (count.compareAndSet(currentCount, nextCount)) { break; } else { currentCount = nextCount; } } } } 在上面的示例代码中,increment()方法会不断尝试将共享资源的计数器从当前值加1。如果成功更新了计数器的值,则说明没有其他线程在同一时间点对共享资源进行了修改,因此可以安全地进行同步操作。否则,increment()方法会继续循环等待,直到获取到锁为止。这样可以确保即使发生竞争条件,也不会导致数据的不一致性问题。
  • [技术干货] 免费漂亮的Java图形验证码
    前言Java 图形验证码,支持自定义图片、中文、算术等类型,可用于 Java Web、JavaSE 等项目。真香!截图展示项目集成package com.kyger;import jakarta.servlet.ServletException;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.Map;public class demo extends HttpServlet { private static final long serialVersionUID = 1L; public demo() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 编码 request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8");; response.setContentType("text/html; charset=utf-8"); // 后台处理 if (request.getMethod().equals("POST")){ String html, appId, appSecret; // 设置 AppId 及 AppSecret,在应用管理中获取 appId = "xxx"; appSecret = "xxx"; KgCaptchaSDK KgRequest = new KgCaptchaSDK(appId, appSecret); // 前端验证成功后颁发的 token,有效期为两分钟 KgRequest.token = request.getParameter("kgCaptchaToken"); // System.out.print(KgRequest.token); // 填写应用服务域名,在应用管理中获取 KgRequest.appCdn = "xxx"; // 请求超时时间,秒 KgRequest.connectTimeout = 5; // 用户登录或尝试帐号,当安全策略中的防控等级为3时必须填写,一般情况下可以忽略 // 可以填写用户输入的登录帐号(如:request.getParameter("username"),可拦截同一帐号多次尝试等行为 KgRequest.userId = "kgCaptchaDemo"; // request 对象,当安全策略中的防控等级为3时必须填写,一般情况下可以忽略 KgRequest.request = request; // java 环境中无法提供 request 对象,请分别定义:clientIp|clientBrowser|domain 参数,即: // KgRequest.clientIp = "127.0.0.1"; // 填写客户端IP // KgRequest.clientBrowser = ""; // 客户端浏览器信息 // KgRequest.domain = "http://localhost"; // 你的授权域名或服务IP // 发送验证请求 Map<String, String> requestResult = KgRequest.sendRequest(); if("0".toString().equals(requestResult.get("code"))) { // 验签成功逻辑处理 *** // 这里做验证通过后的数据处理 // 如登录/注册场景,这里通常查询数据库、校验密码、进行登录或注册等动作处理 // 如短信场景,这里可以开始向用户发送短信等动作处理 // ... html = "<script>alert('验证通过');history.back();</script>"; } else { // 验签失败逻辑处理 html = "<script>alert(\"" + requestResult.get("msg") + " - " + requestResult.get("code") + "\");history.back();</script>"; } response.getWriter().append(html); } else { response.sendRedirect("index.html"); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }}
  • [技术干货] Java中的锁
     在Java中,锁是同步代码块或方法的重要机制之一。当多个线程需要访问共享资源时,锁可以确保这些线程能够安全地访问该资源,避免出现数据竞争和线程安全问题。  Java中的锁分为两种类型:内置锁和显式锁。  内置锁 内置锁也称为监视器锁,它是Java中隐式提供的一种锁。每个对象都有一个相关联的内置锁,可以通过synchronized关键字来获取这个锁。  使用synchronized关键字 synchronized关键字可以用于同步代码块或方法。下面是一个同步代码块的例子:  java public class SyncExample {     private Object lock = new Object();      public void syncMethod() {         synchronized(lock) {             // 同步代码块         }     } } 在上面的例子中,同步代码块使用了lock对象作为内置锁。在执行同步代码块期间,其他试图获取lock对象的线程将被阻塞,直到锁被释放。  synchronized关键字还可以用于同步方法。下面是一个同步方法的例子:  java public class SyncExample {     private Object lock = new Object();      public synchronized void syncMethod() {         // 同步方法体     } } 在上面的例子中,syncMethod()方法将使用SyncExample对象的内置锁进行同步。在执行该方法期间,其他试图访问syncMethod()方法的线程将被阻塞,直到锁被释放。  隐式锁 除synchronized关键字外,Java还提供了一些基于内置锁的类和接口,例如ReentrantLock、ReentrantReadWriteLock和Condition等。这些类和接口提供了更灵活的锁定机制,允许获取和释放锁以及进行条件变量的操作。  显式锁 与内置锁不同,显式锁需要手动获取和释放锁。Java提供了java.util.concurrent.locks.Lock接口来定义这种类型的锁,并提供了一些实现类,例如ReentrantLock和ReentrantReadWriteLock.ReadLock等。  使用Lock接口 下面是一个使用ReentrantLock实现锁的例子:  java public class LockExample {     private Lock lock = new ReentrantLock();      public void lockMethod() {         lock.lock();         try {             // 上锁后要执行的代码块         } finally {             lock.unlock();         }     } } 在上面的例子中,lock()方法将获取锁,unlock()方法将释放锁。与synchronized关键字不同,显式锁可以通过调用tryLock()方法来尝试获取锁而不会阻塞线程。此外,Lock接口还提供了一些其他的便利方法,例如lockInterruptibly()和newCondition()等。  总结 Java中的锁是控制多线程访问共享资源的重要机制。内置锁使用synchronized关键字实现,显式锁使用Lock接口和相关类实现。 
  • [其他] Java中限制并发数的方法
    Java中限制并发数的方法有以下几种:使用synchronized关键字:synchronized可以用于方法或代码块上,它可以保证同一时间只有一个线程执行该代码块或方法。通过在需要控制并发的地方加上synchronized关键字,可以有效地控制并发数。【并发数限制为1】使用ReentrantLock类:ReentrantLock是Java提供的一个可重入的互斥锁,它可以用来实现同步和锁定操作。通过ReentrantLock类可以更加灵活地控制并发数。与synchronized关键字相比,ReentrantLock提供了更加灵活的控制方式,例如可以指定公平锁或非公平锁、超时时间等。使用ReentrantLock需要先创建一个ReentrantLock对象,然后调用lock()方法获取互斥锁,执行完相关操作后调用unlock()方法释放互斥锁。在获取互斥锁时,可以通过tryLock()方法尝试获取锁,如果获取成功则返回true,否则返回false。此外,ReentrantLock还提供了Condition接口,可以实现等待/通知机制。使用Semaphore类:Semaphore是Java提供的一个计数信号量,它可以用来限制同时访问某个资源的线程数量。通过Semaphore类可以实现对并发数的限制。Semaphore是Java提供的一个计数信号量,它可以用来限制同时访问某个资源的线程数量。Semaphore有两个重要方法:acquire()和release()。1>acquire()方法:如果当前线程获取到信号量,则返回true;否则,阻塞等待直到有其他线程释放信号量。2>release()方法:增加信号量的计数器并立即返回。如果此时计数器的值大于指定的上限,则只有已经持有信号量的线程才能继续执行。使用ThreadPoolExecutor类:ThreadPoolExecutor是一个线程池管理类,它可以自动管理线程的创建和销毁,并且可以限制同时运行的线程数量。通过ThreadPoolExecutor类可以更加方便地控制并发数。使用ThreadPoolExecutor需要先创建一个ThreadPoolExecutor对象,然后通过submit()方法提交任务,ThreadPoolExecutor会自动将任务分配给空闲的线程执行。可以通过setCorePoolSize()、setMaxPoolSize()、setKeepAliveTime()等方法来配置线程池的相关参数。
  • [技术干货] JAVA5种字符串拼接
    1.+号拼接2.concat 拼接3.join拼接4.StringBuffer拼接5.StringBuilder拼接public class test { public static void main(String[] args) { String add = add(); String concat = concat(); String join = join(); String buffer = buffer(); String builder = builder(); } public static String add() { String string = ""; long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { string += "a"; } long endTime = System.currentTimeMillis(); System.out.println("+号拼接耗时:" + (endTime - startTime)); return string; } public static String concat() { String string = ""; long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { string = string.concat("a"); } long endTime = System.currentTimeMillis(); System.out.println("concat拼接耗时:" + (endTime - startTime)); return string; } public static String join() { String string = ""; long startTime = System.currentTimeMillis(); List<String> list = new ArrayList<String>(); for (int i = 0; i < 1000000; i++) { list.add("a"); } string = String.join(string, list); long endTime = System.currentTimeMillis(); System.out.println("join拼接耗时:" + (endTime - startTime)); return string; } public static String buffer() { StringBuffer sb = new StringBuffer(); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { sb.append("a"); } long endTime = System.currentTimeMillis(); System.out.println("StringBuffer拼接耗时:" + (endTime - startTime)); return sb.toString(); } public static String builder() { StringBuilder sb = new StringBuilder(); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { sb.append("a"); } long endTime = System.currentTimeMillis(); System.out.println("StringBuilder拼接耗时:" + (endTime - startTime)); return sb.toString(); }} 执行效率+号拼接耗时:6501concat拼接耗时:1739join拼接耗时:13StringBuffer拼接耗时:2     (线程安全,多线程推荐)StringBuilder拼接耗时:1    (线程不安全)
  • [交流吐槽] 求助下,idea全家桶太重了怎么办、
    现在是idea+datagrip+webstorm。 启动项目直接内存拉满 ,
  • [热门活动] 速进,四月份干货合集它来了!!!
     大家好本月为大家带来codeArts技术干货合集。 1.Pandas提取含有指定字符串的行(完全匹配,部分匹配)【转】 https://bbs.huaweicloud.com/forum/thread-0232118247508457054-1-1.html  2.一文带你吃透Python中的日期时间模块【转】 https://bbs.huaweicloud.com/forum/thread-0287118247600909060-1-1.html  3.Linux 安装conda的踩坑日记 https://bbs.huaweicloud.com/forum/thread-02104118248070541037-1-1.html  4.Oracle JDK 和 OpenJDK有什么区别? https://bbs.huaweicloud.com/forum/thread-0214117797316272025-1-1.html  5.JAVA如何实现文件的压缩和解压 https://bbs.huaweicloud.com/forum/thread-0287117797383963030-1-1.html  6.搞清楚TensorFlow2--Keras的Tokenizer https://bbs.huaweicloud.com/forum/thread-0201117864382443016-1-1.html  7.Python德劳内三角剖分详解【转】 https://bbs.huaweicloud.com/forum/thread-0201118245888101041-1-1.html  8.python http通信接口开发示例【转】 https://bbs.huaweicloud.com/forum/thread-0214118246273043053-1-1.html  9.基于Python制作简单的井字棋游戏【转】 https://bbs.huaweicloud.com/forum/thread-0258118246382450049-1-1.html  10.Python中re.findAll()、re.sub()、set()的使用【转】 https://bbs.huaweicloud.com/forum/thread-0287118246466069057-1-1.html  11.Python开发常用五种循环方式的场景性能比较【转】 https://bbs.huaweicloud.com/forum/thread-02104118246598051036-1-1.html  12.Python利用PsUtil实现实时监控系统状态【转】 https://bbs.huaweicloud.com/forum/thread-0214118246712044054-1-1.html  13.Python 程序通过可执行文件部署方式【转】 https://bbs.huaweicloud.com/forum/thread-0287118246833763058-1-1.html  14.Python网络爬虫之获取网络数据【转】 https://bbs.huaweicloud.com/forum/thread-0232118246915486053-1-1.html  15.VBS字符串编码转换函数代码【转】 https://bbs.huaweicloud.com/forum/thread-0214118246992498055-1-1.html  16.批处理echo、echo off、echo on、@、@echo off【转】 https://bbs.huaweicloud.com/forum/thread-0201118247149056042-1-1.html  17.Python嵌套循环的使用【转】 https://bbs.huaweicloud.com/forum/thread-0287118247410572059-1-1.html  18.使用@Autowired为什么会被 IDEA 警告? https://bbs.huaweicloud.com/forum/thread-0287117797109592029-1-1.html  19.剖析JAVA中Stream流的实现原理 https://bbs.huaweicloud.com/forum/thread-0214117797168254024-1-1.html  20.SpringBoot 整合 ElasticSearch 进行各种高级查询 https://bbs.huaweicloud.com/forum/thread-0258117797246098029-1-1.html 
  • [技术干货] 使用Idea 进行合并代码分支(例如:从dev合并到test)
    简介Git(读音为/gɪt/)是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。 [1] 也是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。分布式相比于集中式的最大区别在于开发者可以提交到本地,每个开发者通过克隆(git clone),在本地机器上拷贝一个完整的Git仓库。Git 是基于 Linux内核开发的版本控制工具。与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,不必服务器端软件支持(wingeddevil注:这得分是用什么样的服务端,使用http协议或者git协议等不太一样。并且在push和pull的时候和服务器端还是有交互的。),使源代码的发布和交流极其方便。 Git 的速度很快,这对于诸如 Linux kernel 这样的大项目来说自然很重要。 Git 最为出色的是它的合并跟踪(merge tracing)能力。 实际上内核开发团队决定开始开发和使用 Git 来作为内核开发的版本控制系统的时候,世界开源社群的反对声音不少,最大的理由是 Git 太艰涩难懂,从 Git 的内部工作机制来说,的确是这样。但是随着开发的深入,Git 的正常使用都由一些友好的脚本命令来执行,使 Git 变得非常好用,即使是用来管理我们自己的开发项目,Git 都是一个友好,有力的工具。现在,越来越多的著名项目采用 Git 来管理项目开发。 一、整体合并 团队协作中,开发人员A、B、C分别在dev上进行功能开发,并push代码到远端dev上。当测试人员需要对功能进行测试的时候,我们需要把dev上新增的功能代码合并到test分支上去。  步骤: 1、将dev上的代码push到远端dev上。 2、切换分支到test分支。(就是切换到将要合并的目标分支) 3、拉取代码,确保test分支为远端最新的代码。4、合并分支 5、有冲突,先解决冲突后再合并,没有冲突则合并成功。  6、push代码到远端test分支上去。  二、针对某次提交的合并  团队协作中,开发人员较多,采用上面统一合并分支的形式,如果出现冲突,需要询问对应的开发人员进行代码的取舍,有代码丢失和错乱的风险,所以我们在项目开发中会采用遴选合并分支的方式。(自己写的代码,自己合并到对应的目标分支上去)  步骤:  1、在dev分支上,查看提交历史。 2、复制某次提交的版本号。3、切换到test分支(注意:切换成功后,最好pull拉取下代码,再执行下图命令),按下图执行命令。4、大功告成!———————————————— 原文链接:https://blog.csdn.net/wucaiyun225/article/details/122844223 
  • [技术干货] 解决错误com/fasterxml/jackson/core/StreamWriteFeature nested exception is java.lang.NoClassDefFoundError
    这个错误可能会有很多方面造成的,我就讲下我碰到的几个出错的方面吧 简介Spring是分层的 Java SE/EE应用full- stack轻量级开源框架,以IoC(Inversion of Control:反转控制)和AOP( Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC和持久层 Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE企业应用开源框架。 Jackson 是用来序列化和反序列化 json 的 Java 的开源框架。Spring MVC 的默认 json 解析器便是 Jackson。与其他 Java 的 json 的框架 Gson 等相比, Jackson 解析大的 json 文件速度比较快;Jackson 运行时占用内存比较低,性能比较好;Jackson 有灵活的 API,可以很容易进行扩展和定制。 Jackson 的 1.x 版本的包名是 org.codehaus.jackson ,当升级到 2.x 版本时,包名变为 com.fasterxml.jackson,本文讨论的内容是基于最新的 Jackson 的 2.9.1 版本。 Jackson 的核心模块由三部分组成。 jackson-core,核心包,提供基于"流模式"解析的相关 API,它包括 JsonPaser 和 JsonGenerator。Jackson 内部实现正是通过高性能的流模式 API 的 JsonGenerator 和 JsonParser 来生成和解析 json。 jackson-annotations,注解包,提供标准注解功能; jackson-databind ,数据绑定包, 提供基于"对象绑定" 解析的相关 API ( ObjectMapper ) 和"树模型" 解析的相关 API (JsonNode);基于"对象绑定" 解析的 API 和"树模型"解析的 API 依赖基于"流模式"解析的 API。 1.版本不兼容 spring的版本和jackson的版本不兼容导致的,这个也是我出错的原因,jackson版本2.8.9好像需要spring4.3版本以上,这个我不确定,我的spring版本4.2就出问题了 2.jackson包冲突导致的 如何查看maven的包冲突呢,可以·使用快捷键Ctrl+Shift+Alt+U,或者是打开pom.xml,右键选择maven,然后选择 Show Dependencies 进入依赖包结构之后,可以用走上方的加减图标进行放大和缩小红线的就是有冲突的包,可以选择右键—》exclude把冲突的包排除掉排除冲突的包之后回到pom.xml进行刷新之后冲突的包就被排除了3.没有加入相应的依赖需要加入的jackson依赖有三种,分别为<!-- jackson -->     <dependency>       <groupId>com.fasterxml.jackson.core</groupId>       <artifactId>jackson-databind</artifactId>       <version>${jackson.verson}</version>     </dependency>     <dependency>       <groupId>com.fasterxml.jackson.core</groupId>       <artifactId>jackson-core</artifactId>       <version>${jackson.verson}</version>     </dependency>     <dependency>       <groupId>com.fasterxml.jackson.core</groupId>       <artifactId>jackson-annotations</artifactId>       <version>${jackson.verson}</version>     </dependency> 以上是我目前知道的可能导致错误的三个方面,如果有其他方面会导致这些问题,欢迎留言。原文链接:https://blog.csdn.net/Mustang_carrot/article/details/108364614
  • [技术干货] java 使用File 创建文件和文件夹
    概述: 封装一个磁盘路径字符串,对这个路径可以执行一次操作 可以封装文件路径、文件夹路径、不存在的路径 创建对象 File(String pathname)通过将给定路径名字符串转换为抽象路径名来创建一个新的File实例 new File(“d:/abc/a.txt”); new File(“d:/abc”,”a.txt”);  常用方法 创建: createNewFile()在指定位置创建一个空文件,成功就返回true,如果已存在就不创建,然后返回false。 mkdir() 在指定位置创建一个单级文件夹。 mkdirs() 在指定位置创建一个多级文件夹。 renameTo(File dest)如果目标文件与源文件是在同一个路径下,那么renameTo的作用是重命名, 如果目标文件与源文件不是在同一个路径下,那么renameTo的作用就是剪切,而且还不能操作文件夹。  删除: delete() 删除文件或者一个空文件夹,不能删除非空文件夹,马上删除文件,返回一个布尔值。 deleteOnExit()jvm退出时删除文件或者文件夹,用于删除临时文件,无返回值。  判断: exists() 文件或文件夹是否存在。 isFile() 是否是一个文件,如果不存在,则始终为false。 isDirectory() 是否是一个目录,如果不存在,则始终为false。 isHidden() 是否是一个隐藏的文件或是否是隐藏的目录。 isAbsolute() 测试此抽象路径名是否为绝对路径名。  获取: getName() 获取文件或文件夹的名称,不包含上级路径。 getAbsolutePath()获取文件的绝对路径,与文件是否存在没关系 length() 获取文件的大小(字节数),如果文件不存在则返回0L,如果是文件夹也返回0L。 getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回null。 lastModified()获取最后一次被修改的时间。  文件夹相关: static File[] listRoots()列出所有的根目录(Window中就是所有系统的盘符) list() 返回目录下的文件或者目录名,包含隐藏文件。对于文件这样操作会返回null。 listFiles() 返回目录下的文件或者目录对象(File类实例),包含隐藏文件。对于文件这样操作会返回null。 list(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。 listFiles(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。  /*常用方法测试*/ public class TestFile {     public static void main(String[] args) throws IOException {         //1.创建File类对象         /*1.ready目录与1.txt需要自己手动创建         * 2.File需要导包:import java.io.File;         * 3.路径是String类型,必须写正确,不然找不到文件         * 4.完整的文件名包含两部分:文件名+后缀名*/         File file = new File("E:\\ready\\1.txt");          //2.1测试File中的常用方法         System.out.println(file.length());//3,获取文件的字节量         System.out.println(file.exists());//true,判断文件是否存在         System.out.println(file.isFile());//true,判断是否为文件         System.out.println(file.isDirectory());//false,判断是否为文件夹         System.out.println(file.getName());//1.txt获取文件名         System.out.println(file.getParent());//E:\ready 获取父级路径         System.out.println(file.getAbsolutePath());//E:\ready\1.txt 获取带盘符的完整路径:绝对路径          //2.2 测试创建与删除         /*new 只会帮你在内存中创建一个File类型的对象         * 并不会帮你在磁盘中创建一个真实存在的2.txt文件*/         file = new File("E:\\ready\\2.txt");          //创建一个之前不存在的文件2.txt,如果创建成功,会返回true         /*如果指定创建文件的路径不对,会抛出异常:java.io.Exception         * 所以需要提前处理这个问题,我们暂时选择在main()上抛出         * 这个IO异常是目前我们遇到的强制要求必须预先处理的异常         * 如果不处理,方法的调用会报错,通不过编译*/         System.out.println(file.createNewFile());//创建之前不存在的文件          file = new File("E:\\ready\\m");         System.out.println(file.mkdir());//创建之前不存在的单层文件夹          file = new File("E:\\ready\\a\\b\\c");         System.out.println(file.mkdirs());//创建之前不存在的多层文件夹          System.out.println(file.delete());//c被删除,删除空文件夹或者文件          file = new File("E:\\ready\\a");         System.out.println(file.delete());//false,a文件夹不是空的,里面有内容          file = new File("E:\\ready\\2.txt");         System.out.println(file.delete());//2.txt被删除,可以删除文件          //2.3测试展示文件列表         file = new File("E:\\ready");         String[] list = file.list();/*不常用*/         System.out.println(Arrays.toString(list));         //这句话会报错,因为这是一个String[],所以数组中每个元素都是String类型的         //那么只能用String类中的方法,而isDirectory()是File类中的方法         //System.out.println(list[0].isDirectory());          File[] fs = file.listFiles();/*常用*/         System.out.println(Arrays.toString(fs));         System.out.println(fs[0].isDirectory());     } } 原文链接:https://blog.csdn.net/ldqchat/article/details/123735361
  • [技术干货] List截取的几种方式
    RT: 为了方便先创建一个类的静态属性,并赋值,业务要求,返回n条数据,不足n条全部返回 public class SubList {     private static List<Dog> dogList;     static {         Dog dog1 = Dog.builder().age(8).build();         Dog dog2 = Dog.builder().age(2).build();         Dog dog3 = Dog.builder().age(9).build();         Dog dog4 = Dog.builder().age(9).build();         Dog dog5 = Dog.builder().age(1).build();         Dog dog6 = Dog.builder().age(18).build();         Dog dog7 = Dog.builder().age(10).build();         dogList = Lists.newArrayList(dog1,dog2,dog3);         }         ....     } 1、List原生的sub方法   /**      * 原生的      */     @Test     public void test1(){         List<Dog> dogs = dogList.subList(0, 2);         System.out.println(dogs);     } 1、1源码(可自行DEBUG,ArrayList举例),由下面源码可知道,会先调用subListRangeCheck(…),进行越界检查,因为ArayList底层是数组,数组是有越界问题的,针对不同情况,源码会抛出几个异常,所以我们在使用该方法的时候也要做判断,不足几条就要全部返回 缺点:1、会出现越界问题 需要手动校验 2、会出现空指针异常 需要手动校验      public List<E> subList(int fromIndex, int toIndex) {         subListRangeCheck(fromIndex, toIndex, size);         return new SubList(this, 0, fromIndex, toIndex);     }      static void subListRangeCheck(int fromIndex, int toIndex, int size) {         if (fromIndex < 0)             throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);         if (toIndex > size)             throw new IndexOutOfBoundsException("toIndex = " + toIndex);         if (fromIndex > toIndex)             throw new IllegalArgumentException("fromIndex(" + fromIndex +                                                ") > toIndex(" + toIndex + ")");     } 2、可以使用stream流的方式,JDK8特性,优点,不足的话会全部返回  List<Dog> collect = dogList.stream().limit(10).collect(Collectors.toList()); 3、可以使用一些第三方库,比如hutoo里面的CollectionUtil  //(1)、可以解决空指针问题         dogList = null;         List<Dog> sub = CollectionUtil.sub(dogList, 0, 5);         System.out.println(sub); //null //(2)、可以解决越界问题   我一般开发就用这个工具类比较多 ———————————————— 原文链接:https://blog.csdn.net/weixin_42506330/article/details/107858615 
总条数:2301 到第 页
上滑加载中