Java North

Java 's Blog


  • 首页

  • 分类

  • 作者

  • 归档

  • 关于

MySQL怎么快速插入1亿条数据

发表于 2024-04-14 | 分类于 MySQL

哈喽,大家好,MySQL作为广泛使用的开源关系型数据库管理系统,应该没有Java开发没使用过吧。

关于MySQL,我们大部分时间都在聊,如何提高查询效率,今天我们来聊聊如何提高MySQL的插入效率。

提高插入效率的方式

一般情况下,数据库是运行在专门的服务器上,提高插入效率最明显的当然是提高服务器配置啦。 比如,使用高性能的CPU和SSD磁盘,使用分布式系统架构,将写入压力分散到多个节点。 这个方式的成本也是最高的,老板们当然不会使用这种方式了。

我们还可以从其他方面入手:

  1. 调整数据库配置:优化缓冲池大小、增大批量插入缓冲区等,通过调整MySQL数据库参数的方式。
  2. 选择使用MyISAM存储引擎,因为其简单的表锁机制和无事务开销而在插入速度上表现更优。
  3. 使用批量插入的方式。

考虑到实际的应用场景,我们最可能操作的就是使用第3种实现方式,通过批量插入的方式来提高效率。

探索批量插入

常用的批量插入的方式有2种:

  1. 拼接SQL,使用 insert into xxx (...) values (...),(...),(...)
  2. 利用事务,将批量插入操作封装在单个事务中,可以减少事务开销并提高并发性能。
    1. 在mybatisPlus,以及mybatis-flex中,saveBatch 就是使用的这种方式

接下来我们来测试一下这几个方法。

测试代码

测试的SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE TABLE `orders`  
(  
    `order_id`         BIGINT         NOT NULL AUTO_INCREMENT COMMENT '订单ID(主键)',  
    `customer_id`      BIGINT         NOT NULL COMMENT '客户ID(关联customer表)',  
    `order_status`     tinyint(4)     NOT NULL DEFAULT 1 COMMENT '订单状态 1-待支付 2-已支付 3-待发货 4-已发货 5-已完成 6-已取消',  
    `payment_method`   tinyint(4)     NULL     DEFAULT null COMMENT '支付方式; 1-现金 2-支付宝 3-微信 4-银行卡',  
    `total_amount`     DECIMAL(10, 2) NOT NULL COMMENT '订单总金额',  
    `shipping_fee`     DECIMAL(10, 2) NOT NULL DEFAULT 0 COMMENT '运费',  
    `coupon_discount`  DECIMAL(10, 2) NOT NULL DEFAULT 0 COMMENT '优惠券减免金额',  
    `order_date`       DATETIME       NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '下单日期',  
    `payment_time`     DATETIME                DEFAULT NULL COMMENT '支付时间',  
    `shipping_address` VARCHAR(255)   NULL COMMENT '收货地址',  
    `receiver_name`    VARCHAR(50)    NULL COMMENT '收货人姓名',  
    `receiver_phone`   VARCHAR(20)    NULL COMMENT '收货人电话',  
    PRIMARY KEY (`order_id`)  
) ENGINE = InnoDB  
  DEFAULT CHARSET = utf8mb4 COMMENT ='订单信息表';

一、使用 batchXml

1
2
3
4
5
insert into orders (order_id, customer_id, order_status, payment_method, order_date, total_amount, shipping_fee, coupon_discount)  
values  
<foreach collection="orders" item="item" separator=",">  
    (#{item.orderId}, #{item.customerId}, #{item.orderStatus}, #{item.paymentMethod}, #{item.orderDate}, #{item.totalAmount}, #{item.shippingFee}, #{item.couponDiscount})  
</foreach>

二、使用mybatis-flex提供的saveBatch

1
ordersService.saveBatch(list);

三、手动控制事务的提交,saveBatchSession

1
2
3
4
5
6
7
8
9
10
public void saveBatchSession(List<Orders> orders) {  
    SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);  
    OrdersMapper mapper = session.getMapper(OrdersMapper.class);  
    for (int i = 0,length = orders.size(); i < length; i++) {  
        mapper.insert(orders.get(i));  
    }  
    session.commit();  
    session.clearCache();  
    session.close();  
}

启动代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test  
public void generatorTestData() {  
	genOrders(0L, 100000L);  
}

private void genOrders(long start, long end) {  
	List<Orders> list = new ArrayList<>();  
	long s = System.currentTimeMillis();  
	for (long i = start + 1; i <= end; i++) {  
		if ((i - start) % 1000 == 0) {  
			ordersService.saveBatchSession(list);  
//          ordersService.saveBatchXml(list);   
//			ordersService.saveBatch(list);  
			list.clear();  
			itemAll.clear();  
			System.out.println("生成数据:" + (i - start) + "条,耗时:" + (System.currentTimeMillis() - s) + "ms");  
			s = System.currentTimeMillis();  
			continue;  
		}  
		// 构建所有属性  
		list.add(Orders.builder() ... .build());
	}  
	ordersService.saveBatch(list);  
}

测试结果

使用了3种方式进行测试

未开启批处理,batchXml image.png

未开启批处理,mybatis-flex提供的saveBatch image.png

未开启批处理,saveBatchSession

image.png

从这里的结果可以看出,使用 batchXml 的效率是最高的,远远超越其他方式。 但是仔细一想,这些数据应该很不正常,插入1000条数据,竟然需要4秒左右,和单条插入1000次的时间几乎没有区别。

开启批处理

经过一番查询资料,并检查配置,发现果然另有玄机,连接数据库的时候没有开启批处理

开启方式:在spring的配置文件中,连接数据源时,url需要增加 allowPublicKeyRetrieval=true

然后重新测试一遍。

开启批处理,saveBatchXml image.png

开启批处理,mybatis-flex提供的saveBatch image.png

开启批处理,saveBatchSession image.png

这次的结果就比较正常了,可以看出来:

  • saveBatchSession最快
  • mybatis-flex提供的saveBatch 因为有些额外的操作,多消耗了10ms左右的时间
  • saveBatchXml 相较于另外两种方式,慢了30ms~40ms。

接下来,把每批次的处理数据由1000次增加到10000次,再次进行测试。

开启批处理,saveBatchXml,10000条一批次 image.png

开启批处理,saveBatchSession,10000条一批次 image.png

开启批处理,mybatis-flex提供的saveBatch,10000条一批次 image.png

由此结果可以看出来:

  • saveBatchSession和mybatis-flex提供的saveBatch 耗时基本一致
  • saveBatchXml就明显的慢一些,按照效率差算,差了将近50%的效率 \(\text { 效率差 }=\frac{\text { 较长时间 }- \text { 较短时间 }}{\text { 较短时间 }}=\frac{1200 \mathrm{~ms}-800 \mathrm{~ms}}{800 \mathrm{~ms}}\)

总结

综上,提高MySQL插入效率主要可通过调整数据库配置、选择适合的存储引擎以及运用批量插入策略等方式实现。 在实际应用中,尤其是在使用ORM框架进行数据操作时,应合理选择并充分利用批量插入功能,以最大程度提升插入效率。

省流

  1. 连接数据时,数据源配置文件url加上 allowPublicKeyRetrieval=true
  2. 数据量很大时,使用mybatisPlus或MybatisFlex提供的 saveBatch即可。
阅读全文 »

CompletableFuture:Java 8 中的异步编程利器

发表于 2024-04-14 | 分类于 异步编程

在现代软件开发中,异步编程已成为提升系统性能、响应能力和可扩展性的关键手段。Java 8 引入了 CompletableFuture 类,为 Java 平台带来了强大的异步编程能力。

本篇文章将带你认识这个异步编程神器:CompletableFuture。

什么是 CompletableFuture

CompletableFuture 是 Java 8 引入的 java.util.concurrent 包下的一个类,它代表一个异步计算的结果,可以是已完成、正在进行或尚未开始。 CompletableFuture 提供了一种灵活、类型安全的方式来表达异步操作的生命周期,包括创建、组合、处理结果以及处理异常。其设计灵感来源于函数式编程中的 Promises/Futures 模式,旨在简化异步编程模型,提高代码的可读性和可维护性。

CompletableFuture 的核心功能

1. 创建 CompletableFuture

a. completedFuture(T value)

completedFuture(T value) 是一个静态工厂方法,用于创建一个已经处于完成状态且包含给定结果值的 CompletableFuture。这适用于预先计算好的结果或常量值,使得其他组件可以以异步形式消费。

b. supplyAsync(Supplier<U> supplier, Executor executor)

supplyAsync() 方法接受一个 Supplier 函数和一个可选的 Executor,异步执行 supplier.get(),并将结果封装到一个新的 CompletableFuture 中。计算在 Executor 管理的线程中进行,不阻塞当前线程。

c. runAsync(Runnable runnable, Executor executor)

类似于 supplyAsync(),runAsync() 接受一个 Runnable 任务和一个 Executor,异步执行任务。由于 Runnable 没有返回值,runAsync() 返回的 CompletableFuture 完成时没有结果。

2. 组合 CompletableFuture

a. thenApply(Function<? super T,? extends U> fn)

在当前 CompletableFuture 完成后,应用给定的 Function 处理结果,并返回一个新的 CompletableFuture,其结果为 Function 应用后的值。

b. thenAccept(Consumer<? super T> action)

当当前 CompletableFuture 完成后,执行给定的 Consumer 消费结果。由于 Consumer 没有返回值,返回的 CompletableFuture 完成时没有结果。

c. thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)

当当前 CompletableFuture 与另一个 CompletionStage(如另一个 CompletableFuture)都完成时,应用给定的 BiFunction 合并两个结果,并返回一个新的 CompletableFuture。

3. 异常处理

a. exceptionally(Function<Throwable,? extends T> fn)

当当前 CompletableFuture 因异常而未能正常完成时,应用给定的 Function 处理异常,并返回一个新的 CompletableFuture,其结果为 Function 应用后的值。

b. handle(BiFunction<? super T, Throwable, ? extends U> fn)

无论当前 CompletableFuture 正常完成还是因异常未能完成,都会应用给定的 BiFunction 处理结果或异常,并返回一个新的 CompletableFuture。

4. 其他重要方法

a. allOf(CompletableFuture<?>... cfs)

创建一个新的 CompletableFuture,当所有给定的 CompletableFuture 都完成(不论成功与否)时,新 CompletableFuture 完成。

b. anyOf(CompletableFuture<?>... cfs)

创建一个新的 CompletableFuture,当任意一个给定的 CompletableFuture 完成时,新 CompletableFuture 完成。

实战应用

CompletableFuture 的使用场景很广泛,例如

  1. 异步数据库查询与结果合并
  2. 微服务间异步通信
  3. 并行任务执行与结果汇总
  4. 异步事件处理与通知

这里以第一个场景举例: 场景:在一个订单处理系统中,需要查询订单的详细信息、关联的商品信息以及用户的个人信息。为减少查询延迟,可以使用 CompletableFuture 对每个查询进行异步执行,并在所有查询完成后合并结果。 示例:

如果我们不使用Java8提供的这个CompletableFuture 来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

@Service
@RequiredArgsConstructor
public class OrderProcessingServiceLegacy {
    private final OrderRepository orderRepo;
    private final ProductRepository productRepo;
    private final UserRepository userRepo;

    public OrderDetails fetchOrderDetails(int orderId) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        CountDownLatch orderLatch = new CountDownLatch(1);
        CountDownLatch productsLatch = new CountDownLatch(1);
        CountDownLatch userLatch = new CountDownLatch(1);

        Order order = null;
        List<Product> products = null;
        User user = null;

        // 异步查询订单
        executor.execute(() -> {
            try {
                order = orderRepo.findOrderById(orderId);
                orderLatch.countDown();
            } finally {
                productsLatch.countDown();
            }
        });

        // 异步查询商品
        executor.execute(() -> {
            try {
                products = productRepo.findProductsByOrderId(orderId);
            } finally {
                productsLatch.countDown();
            }
        });

        // 异步查询用户(等待订单查询完成后再执行)
        executor.execute(() -> {
            try {
                orderLatch.await(); // 确保订单查询已完成
                user = userRepo.findUserById(order.getCustomerId());
            } finally {
                userLatch.countDown();
            }
        });

        // 等待所有查询完成
        userLatch.await();

        return new OrderDetails(order, products, user);
    }

    // ... 其他方法 ...
	@Data
	@AllArgsConstructor 
    public static class OrderDetails {
        private final Order order;
        private final List<Product> products;
        private final User user;
    }
}

使用CompletableFuture实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Service
@RequiredArgsConstructor
public class OrderProcessingService {
    private final OrderRepository orderRepo;
    private final ProductRepository productRepo;
    private final UserRepository userRepo;
	private final ThreadPoolExecutor executor;


    public CompletableFuture<OrderDetails> fetchOrderDetails(int orderId) {

        CompletableFuture<Order> orderFuture = CompletableFuture.supplyAsync(() -> orderRepo.findOrderById(orderId), executor);
        CompletableFuture<List<Product>> productsFuture = CompletableFuture.supplyAsync(() -> productRepo.findProductsByOrderId(orderId), executor);
        CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> userRepo.findUserById(order.getCustomerId()), executor);

        return CompletableFuture.allOf(orderFuture, productsFuture, userFuture)
                .thenApplyAsync(unused -> {
                    Order order = orderFuture.join();
                    List<Product> products = productsFuture.join();
                    User user = userFuture.join();

                    return new OrderDetails(order, products, user);
                }, executor);
    }

    // ... 其他方法 ...
	@Data
    public static class OrderDetails {
        private final Order order;
        private final List<Product> products;
        private final User user;

    }
}

在这个示例中:

  • 使用了CompletableFuture之后,代码量减少了,整洁度和可读性也得到提高。
  • fetchOrderDetails 方法接受一个订单 ID,使用 CompletableFuture.supplyAsync() 异步查询订单、商品和用户信息。
  • 使用 CompletableFuture.allOf() 监控所有查询的完成状态。
  • 当所有查询完成时,使用 thenApplyAsync() 合并结果,创建一个包含完整订单详情的 OrderDetails 对象。

小结

CompletableFuture 作为 Java 8 引入的重要异步编程工具,极大地提升了 Java 平台在应对高并发、高性能场景的能力。 结合 Java 8 的并行流(Stream.parallel())与 CompletableFuture,可以轻松实现数据集的并行处理和结果聚合。 下次给大家聊聊Stream.parallel()。

阅读全文 »

程序员遇上PPT

发表于 2024-03-27 | 分类于 sql

哈喽,大家好,我是了不起。

当你遇到PPT的时候,你的内心是什么样子的。

如果需要你写PPT了,那么有几个时间点,你要进行述职汇报,大概率是升职了。或者作为程序员,你的代码之路逐渐走向尾声,以后更多的是需要进行汇报工作,此时你需要领导他人,然后再也没有时间写代码了。关于PPT,每一个程序员都应该重视,毕竟这是你保留饭碗的一项硬技能!

阅读全文 »

字节一面:TCP和UPD能使用同一个端口吗?-20240317

发表于 2024-03-17 | 分类于 Java

哈喽,大家好,我是了不起。

今天在知乎看到一个字节跳动的面试题,感觉还有点意思,分享给大家。

阅读全文 »

为什么都说 HashMap 是线程不安全的?-20240322

发表于 2024-03-17 | 分类于 Java

哈喽,大家好,我是了不起。

做Java开发的人,应该都用过 HashMap 这种集合。

今天就和大家来聊聊,为什么 HashMap 是线程不安全的。

阅读全文 »

Java对象不再使用时,为什么要赋值为 null ?-20240320

发表于 2024-03-17 | 分类于 Java

哈喽,大家好,我是了不起。

相信大家在面试Java开发的时候,会遇到比较多的问题是Java的内存管理,这里面涉及到Java垃圾回收机制,以及JVM调优等等,那么今天跟大家讨论一个问题:Java对象不再使用时,为什么要赋值为 null ?

阅读全文 »

为什么不建议使用Date类

发表于 2024-03-10 | 分类于 Java

在Java编程中,日期和时间处理是一个常见但也是复杂的任务。尽管Java提供了内置的Date类来处理日期和时间,但在实际开发中,我们常常遇到一些问题和挑战,因此不建议过度依赖Date类。本文将深入探讨这些问题,并提供一些替代方案,以帮助开发人员更好地处理日期和时间。

线程安全问题

Date类是可变的,这意味着它的状态可以在对象创建后被修改。因此,如果多个线程同时访问和修改同一个Date对象,可能会导致竞态条件或其他并发问题。例如,考虑以下代码片段:

1
2
3
4
5
6
7
8
Date currentDate = new Date();

// 线程1
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
String formattedDate = formatter.format(currentDate);

// 线程2
currentDate.setTime(0);

在这个例子中,如果线程1正在格式化日期时,线程2修改了当前日期对象的时间,那么最终的格式化结果可能会与预期不符。

日期格式化困难

Date类虽然可以表示一个特定的日期和时间,但它不提供直接的方法来格式化日期。通常情况下,我们需要使用SimpleDateFormat类来格式化Date对象,但这也会带来一些问题。例如,考虑以下代码:

1
2
3
Date currentDate = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
String formattedDate = formatter.format(currentDate);

这段代码看起来很简单,但是如果在多线程环境中使用同一个SimpleDateFormat实例,会导致线程安全问题。此外,SimpleDateFormat的模式字符串不易于记忆和理解,容易出错。

过时的API

Date类及其相关的API在Java 8中被认为是过时的。取而代之的是java.time包中的新日期和时间API。这些新API提供了更丰富的功能,更好的类型安全性和不可变性,以及更好的设计来应对一些常见的日期和时间问题。例如,考虑使用新API创建一个表示当前日期的示例:

1
LocalDate currentDate = LocalDate.now();

这比使用Date类简单得多,并且避免了Date类的一些问题。

时区问题

Date类在处理时区时可能会出现问题。它默认使用系统的时区,这可能会导致在跨时区应用程序中产生错误的日期和时间计算。例如,考虑以下代码:

1
2
Date currentDate = new Date();
System.out.println(currentDate);

这段代码在不同的时区中可能会产生不同的输出,这取决于系统的默认时区设置。

替代方案

  1. 如果你是使用的jdk8以及之后的版本,建议使用java.time包: 使用Java 8引入的新日期和时间API,如LocalDate、LocalTime、LocalDateTime等。这些类是不可变的、线程安全的,而且提供了更丰富的功能,更适合现代Java应用程序的需求。

  2. 如果你是使用的jdk7以及之前的版本,建议使用第三方库来操作时间: 如Joda-Time。Joda-Time提供了类似于java.time的功能,并且在Java 8之前广受欢迎。

JDK8 常用时间API

Java 8引入了全新的日期和时间API,位于java.time包中,用于更方便、更安全地处理日期和时间。这个API提供了许多新的类和方法,以及更丰富的功能,旨在解决以前Date类所存在的问题。下面详细介绍Java 8的时间API,并举一些使用示例:

  1. LocalDate:表示日期,不包含时间信息,是不可变的。
1
2
LocalDate today = LocalDate.now(); // 当前日期
LocalDate birthday = LocalDate.of(2024, 3, 11); // 指定日期
  1. LocalTime:表示时间,不包含日期信息,也是不可变的。
1
2
LocalTime now = LocalTime.now(); // 当前时间
LocalTime lunchTime = LocalTime.of(12, 30); // 指定时间
  1. LocalDateTime:表示日期和时间,不包含时区信息,同样是不可变的。
1
2
LocalDateTime currentTime = LocalDateTime.now(); // 当前日期和时间
LocalDateTime specificDateTime = LocalDateTime.of(2023, Month.JULY, 4, 13, 30); // 指定日期和时间
  1. ZonedDateTime:表示带有时区的日期和时间,可以明确表示不同时区的日期时间信息。
1
2
3
ZonedDateTime currentDateTime = ZonedDateTime.now(); // 当前日期和时间(带时区)
ZoneId newYorkZone = ZoneId.of("America/New_York");
ZonedDateTime newYorkTime = ZonedDateTime.now(newYorkZone); // 纽约当前日期和时间
  1. Duration:表示两个时间点之间的时间间隔。
1
2
3
4
LocalDateTime start = LocalDateTime.of(2023, Month.JANUARY, 1, 8, 0);
LocalDateTime end = LocalDateTime.of(2023, Month.JANUARY, 1, 12, 30);
Duration duration = Duration.between(start, end);
long hours = duration.toHours(); // 计算小时数
  1. DateTimeFormatter:用于格式化和解析日期时间。
1
2
3
4
LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = dateTime.format(formatter); // 格式化为字符串
LocalDateTime parsedDateTime = LocalDateTime.parse("2023-07-04 13:30:00", formatter); // 解析字符串为日期时间
  1. TemporalAdjusters:提供了各种调整日期的方法。
1
2
3
LocalDate firstDayOfMonth = LocalDate.now().with(TemporalAdjusters.firstDayOfMonth()); 
// 获取本月的第一天
LocalDate nextTuesday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.TUESDAY)); // 获取下一个星期二

通过使用这些新的类和方法,Java 8的时间API使得处理日期和时间变得更加简单、直观和安全。它提供了更丰富的功能和更好的设计,可以更好地满足现代Java应用程序的需求,并避免了以前Date类所存在的问题。

阅读全文 »

一个熟悉又陌生的关键字:volatile

发表于 2024-03-10 | 分类于 Java

hello,今天带大家了解一下这个熟悉又陌生的关键字:volatile。

在Java多线程编程中,保证线程安全性是至关重要的。而volatile关键字是实现线程安全性的一种关键机制。

为什么熟悉又陌生呢?Java开发者几乎全都用到过这个关键字,但是又不记得什么时候用了它。

1. volatile关键字的原理

volatile关键字主要用于保证变量在多线程环境下的可见性和禁止指令重排序。

当一个变量被volatile修饰时,线程在读取这个变量的值时将直接从主内存中读取,而不是从线程的本地缓存中读取。

同样地,当一个线程修改了volatile变量的值时,这个变化将立即写回到主内存中,而不是仅仅保存在线程的本地缓存中。

2. volatile关键字的作用

  • 保证可见性:在多线程环境下,如果一个线程修改了volatile变量的值,那么其他线程将立即看到这个变化。这样可以避免线程间的数据不一致性问题。
  • 禁止指令重排序:volatile关键字还可以防止编译器和处理器对代码的优化,确保指令按照程序的顺序执行,避免出现意料之外的行为。

3. volatile关键字的正确使用方法

  • 适用场景:volatile适用于那些被多个线程访问但并不涉及复合操作(例如递增操作)的变量。典型的使用场景包括状态标志、控制变量等。
  • 不适用场景:不要将volatile用于需要原子性操作的场景,因为volatile并不能保证原子性。对于需要原子性操作的场景,应该使用锁或者Atomic原子类。

4. 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class VolatileExample {
    private volatile boolean flag = false;

    public void startTask() {
        // 启动一个线程来修改flag的值
        new Thread(() -> {
            try {
                Thread.sleep(1000); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true;
            System.out.println("Flag has been set to true.");
        }).start();
    }

    public void monitorTask() {
        // 启动一个线程来检查flag的值
        new Thread(() -> {
            while (!flag) {
                // 循环等待,直到flag变为true
            }
            System.out.println("Flag is now true. Task can proceed.");
        }).start();
    }

    public static void main(String[] args) {
        VolatileExample example = new VolatileExample();
        example.startTask();
        example.monitorTask();
    }
}

在这个示例中,我们有两个线程,一个线程调用startTask()方法来修改flag的值为true,另一个线程调用monitorTask()方法来检查flag的值是否为true。在flag没有被volatile修饰的情况下,可能会出现monitorTask()方法陷入死循环的情况,因为它无法及时获取到flag的最新值。但是,由于flag被volatile修饰,线程可以立即看到flag的变化,因此可以正确地退出循环,从而避免了可能出现的问题。

实际应用

事实上,这个简单的示例代码,在实际使用中,几乎是用不到它这种写法;那到底是怎么使用的这个volatile呢?

其实在Java中,java.util.concurrent.atomic包提供了一组原子类,比如AtomicInteger、AtomicLong、AtomicBoolean等,它们提供了一种无锁的线程安全机制,以确保对变量的操作是原子性的。

当谈到Atomic原子类的实现原理时,CAS(Compare and Swap)操作是其中的关键。CAS是一种乐观锁技术,它涉及比较内存中的值和预期值,如果相等,则使用新值替换内存中的值。在Java中,CAS是通过Unsafe类实现的,它是一种硬件级别的原子性操作。

但是,CAS操作本身无法解决线程可见性的问题,这就是volatile关键字的作用。volatile关键字可以确保变量的写操作立即可见于其他线程,从而解决了线程之间的可见性问题。因此,Atomic原子类是结合了CAS和volatile关键字来实现线程安全。

因此,结合了CAS和volatile关键字,Atomic原子类能够在无锁的情况下实现线程安全,提供了一种高效的并发编程解决方案。CAS保证了原子性,volatile保证了可见性,两者结合起来提供了一个强大的多线程环境下的并发控制机制。

小结

日常开发中,我们一般情况下都是直接使用的Atomic原子类来保证线程安全的情况,并不会去直接使用volatile关键字,乍一看这个volatile还真是熟悉又陌生呢!

阅读全文 »

Session、Cookie、JWT

发表于 2024-02-20 | 分类于 sql

哈喽,大家好,我是了不起。

日常开发中或者面试的时候经常会遇到身份验证的问题,下面这些都是关于身份验证和授权的常用技术。

Session、Cookie、JWT、Token、SSO和OAuth 2.0 这些都是什么,在我们的应用程序中有什么用,我们来看看!

阅读全文 »

使用Docker搭建了一个幻兽帕鲁服务器

发表于 2024-01-29 | 分类于 服务器

幻兽帕鲁是一款支持多人游戏模式的全新开放世界生存制作游戏,主打怪物养成、战斗、领域探索、建造和制作等核心玩法。

在 Steam 平台一经上线就备受好评,24 小时内销量破 200 万份,steam同时在线排行榜第二,第一名是前几天大火的绝地求生。

阅读全文 »

我为什么不用gitlab做私有Git服务器

发表于 2024-01-29 | 分类于 Git

为什么要考虑自己搭建和部署私有Git服务器呢?

一方面,自托管的代码托管平台可以给团队提供更高的灵活性和定制化能力。可以根据团队的需求和安全要求进行自定义配置,而不受公共托管平台的限制。另一方面,自己搭建代码托管平台还可以加强数据的安全性和隐私保护,减少了数据泄露和安全漏洞的风险。

阅读全文 »

redis发布订阅

发表于 2024-01-09 | 分类于 sql

哈喽,大家好,我是了不起。

Redis平常作为缓存使用较多,但是也可以作为发布订阅的消息队列来使用,本篇给大家介绍一下如何简单使用!

阅读全文 »

数据库优化

发表于 2024-01-06 | 分类于 sql

哈喽,大家好,我是了不起。

面试的时候总有人会问数据库优化,有没有想过为什么面试官会这么注重数据库,我为大家总结了几点原因。

最近有兄弟想要看看数据库如何优化,那么我总结整理了数据库优化的一些方案。

  1. 性能需求:随着数据量的增长和应用程序的复杂度增加,数据库的性能可能会成为瓶颈。优化数据库可以提高查询速度、数据插入和更新的效率,从而提升整个应用程序的性能。
  2. 资源利用:数据库可能占用大量的系统资源,如内存、CPU 和磁盘 I/O。通过优化数据库,可以减少资源的消耗,提高系统的整体效率。
  3. 数据量增长:随着时间的推移,数据库中的数据量可能会迅速增长。优化可以帮助处理大规模数据集,确保数据库在处理大量数据时仍能保持良好的性能。
  4. 应用程序扩展:当应用程序需要支持更多用户、处理更多并发请求或扩展到新的功能时,数据库可能需要进行优化以满足新的需求。
  5. 竞争压力:在竞争激烈的市场中,优化数据库可以提供更好的用户体验,提高应用程序的竞争力。
  6. 成本考虑:优化数据库可以降低硬件成本和运营成本。通过提高性能和效率,可以减少所需的服务器资源和能源消耗。
  7. 维护和管理:优化数据库有助于减少维护工作和故障排除的时间。良好的优化可以提高数据库的稳定性和可靠性。
阅读全文 »

大整数传输为何禁用Long类型?-20240105

发表于 2023-12-28 | 分类于 Java

哈喽,大家好,我是了不起。

在现代编程中,处理大整数是一个常见的需求,特别是在需要进行网络传输或数据交换的场合。

尽管Java中的 Long 类型在很多情况下都非常有用,但在处理特别大的整数或进行跨系统的数据传输时,一定不要使用 Long 类型

阅读全文 »

为什么阿里建议你不要使用Executors来创建线程池?-20240101

发表于 2023-12-28 | 分类于 Java

哈喽,大家好,我是了不起。

阿里作为国内Java使用最多的大厂,他出版了一部《阿里巴巴Java开发手册》,不知道大家看过没,没有看过的话,建议大家看看。

对于我们编程养成良好的习惯还是很有帮助的,最近我在看到并发这一规约的时候,他们就明确了一点:线程池不允许使用 Executors来创建。

阅读全文 »

5年程序员使用ArrayList居然用forEach遍历删除元素?-20240101

发表于 2023-12-28 | 分类于 Java

哈喽,大家好,我是了不起。

通常1-3年工作经验的程序员算是初级程序员,再往后基本上就是在编程领域有了一定经验的高级程序员了。

但是最近公司代码review时,我居然发现一个 5 年工作经验的程序员,使用 ArrayList 居然用 forEach 遍历删除元素?

阅读全文 »

探索分布式 Session 管理

发表于 2023-12-24 | 分类于 Auth

随着云计算和微服务架构的兴起,分布式系统已经成为现代应用程序的标配。

在分布式系统中,最常用的解决方案之一就是使用Token的无状态认证方式。今天带大家学习另一种分布式系统下权限认证的实现方案——分布式Session。

阅读全文 »

一款开源的linux可视化管理项目,运维效率翻倍

发表于 2023-12-24 | 分类于 Linux

大家好,我是了不起。

linux是个非常好的开源操作系统,功能强大,使用也非常广泛,服务器的运维管理主要依赖命令行操作,但是这种方式对于普通人来说,晦涩难懂,相对复杂,今天介绍一款开源的linux 可视化管理项目1panel。

阅读全文 »

彻底弄明白Session 和 Token

发表于 2023-12-24 | 分类于 Auth

大家好,我是了不起。

在构建用户身份管理系统时,选择会话(Session)还是令牌(Token)是一个关键决策,取决于系统的需求和特定的使用场景。本文将深入探讨何时适合使用会话,何时适合使用令牌,以帮助开发人员在实际应用中做出明智的选择。

阅读全文 »

Redis 热 Key 发现以及解决办法

发表于 2023-12-09 | 分类于 Redis

哈喽,大家好,我是了不起。 最近一直在处理Redis的一些问题,给大家分享一下看到的一篇关于Redis热点key的文章。

阅读全文 »
1 2 3 … 19
Java Geek Tech

Java Geek Tech

一群热爱 Java 的技术人

379 日志
126 分类
56 作者
RSS
GitHub 知乎
Links
  • 纯洁的微笑
© 2019 - 2025 Java Geek Tech
由 Jekyll 强力驱动
主题 - NexT.Mist