并发编程-共享式AQS源码详解

上篇文章详细的阐述了AQS在独占模式下的底层原理,本篇主要讲述共享式同步器的原理。

1. acquireShared(int)

此方式是共享模式下线程获取贡献资源的入口,他会获取指定量的资源,获取成功直接返回,失败则进入等待队列,知道获取到资源为止,整个过程忽略终端。下面看源码:

1
2
3
4
5
public final void acquireShared(int arg) {
//
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
1
2
3
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}

这里 tryAcquireShared 依然需要自定义同步器去实现,但是AQS已经将返回值的语义定义好了,重载该函数的时候执行逻辑要符合下列语义:
-返回负值表示获取失败

查看更多

nginx入门

Nginx是一款自由的、开源的、高性能的HTTP服务器和反向代理服务器;同时也是一个IMAP、POP3、SMTP代理服务器;Nginx可以作为一个HTTP服务器进行网站的发布处理,另外Nginx可以作为反向代理进行负载均衡的实现。

1、正向代理与反向代理

1.1 正向代理:代理服务器代表的是客户端,代理对服务器端透明。

正向代理的应用场景:

  • vpn
  • 缓存,加速访问资源
查看更多

SpringBoot-数据校验

参数验证是一个常见的问题,无论是前端还是后台,都需对用户输入进行验证,以此来保证系统数据的正确性。对于web来说,有些人可能理所当然的想在前端验证就行了,但这样是非常错误的做法,前端代码对于用户来说是透明的,稍微有点技术的人就可以绕过这个验证,直接提交数据到后台。无论是前端网页提交的接口,还是提供给外部的接口,参数验证随处可见,也是必不可少的。前端做验证只是为了用户体验,比如控制按钮的显示隐藏,单页应用的路由跳转等等。后端才是最终的保障,总之,对于后端接口来说,一切用户的输入都是不可信的。

1、依赖关系

1
compile 'org.springframework.boot:spring-boot-starter-validation'

该依赖在spring-boot-starter-web中已经引入,如果是springboot Web项目,则不用再单独添加依赖。

springboot的数据绑定和数据校验功能在org.springframework.validation包中,@Validated注解就是在此定义的。

validation包实现参数校验主要通过调用Jakarta.Validation-api.jar包,该jar包定义了一套参数验证的接口,没有具体实现,我们常用的约束注解就是定义在此处。

查看更多

并发编程-容器之CopyOnWrite

Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。

1. 什么是CopyOnWrite容器

CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

2. CopyOnWriteArrayList的实现原理

在使用CopyOnWriteArrayList之前,我们先阅读其源码了解下它是如何实现的。以下代码是向CopyOnWriteArrayList中add方法的实现(向CopyOnWriteArrayList里添加元素),可以发现在添加的时候是需要加锁的,否则多线程写的时候会Copy出N个副本出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。

查看更多

并发编程-容器ConcurrentHashMap源码分析

1.重要的属性

首先来看几个重要的属性,与HashMap相同的就不再介绍了,这里重点解释一下sizeCtl这个属性。可以说它是ConcurrentHashMap中出镜率很高的一个属性,因为它是一个控制标识符,在不同的地方有不同用途,而且它的取值不同,也代表不同的含义。

  • 负数代表正在进行初始化或扩容操作
  • -1代表正在初始化
  • -N 表示有N-1个线程正在进行扩容操作
查看更多

并发编程-线程池源码详解

阿里巴巴Java手册有一条:

【强制】线程资源必须通过线程池提供,禁止在应用程序中显示创建线程。

说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程导致消耗完内存或者过度切换的问题。

简单来说使用线程池有以下几个目的:

查看更多

并发编程-独占式AQS源码详解

1. 框架概述

AQS是AbstractQueuedSynchronizer的简称,抽象队列同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类的实现都依赖于它,比如常用的ReentrantLock/CountDownLatch/Semaphore

AQS维护了一个volatile int state 代表共享资源,一个FIFO线程等待队列用来记录争用资源而进入等待的线程,这里有一点需要强调,AQS同步队列中的线程是处于WAITING状态的,而竞争synchronized同步块的线程是处于BLOCKED状态的

线程获取AQS框架下的锁先是尝试CAS乐观锁去获取,获取不到才会转换为悲观锁,如线程获取ReentrantLock在CAS阶段是处于RUNNABLE状态的,获取失败进入等待队列才会转换成WAITING状态。

AQS定义了两种组员共享方式:Exclusive 和 Share

用户自定义同步器时只需要实现共享资源state的获取与释放方式,至于具体的线程等待队列的维护,AQS已经实现好了。自定义同步器需要实现的几个方法:

查看更多

Mybatis-批量操作

批量插入

1
2
3
4
5
6
7
8
9
10
11
12
13
@Insert("<script>" +
"INSERT INTO patent_post_info(patent_id,post_time,post_information) values "+
"<foreach collection =\"postInfos\" item=\"postInfo\" index= \"index\" separator =\",\">" +
"("+
"#{patentId}, " +
"CAST (#{postInfo.postTime} AS timestamp),"+
"#{postInfo.postInformation}"+
")" +
"</foreach >"+
"</script>"
)
Integer insertPatentPostInfo(@Param("patentId")Integer id, @Param("postInfos")List<PatentPostInfo> postInfos)
throws SQLException;

批量更新

以下示例展示了更新两个字段,每一个字段使用一个片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Update("<script>"+
"UPDATE order_items " +
"SET " +
"goods_total_price=" +
"<foreach collection=\"orderItems\" item=\"orderItem\"index=\"index\" separator=\" \" open=\"CASE id\" close=\"END\">" +
"WHEN #{orderItem.id} THEN #{orderItem.goodsTotalPrice}" +
"</foreach>" +
",goods_name="+
"<foreach collection=\"list\" item=\"orderItem\" index=\"index\" separator=\" \" open=\"CASE id\" close=\"END\">" +
"WHEN #{orderItem.id} THEN #{orderItem.goodsName}" +
"</foreach>" +
"WHERE id IN " +
"<foreach collection=\"list\" item=\"orderItem\" index=\"index\" separator=\",\" open=\"(\" close=\")\">" +
"#{orderItem.id}"+
"</foreach>" +
"</script>")
Integer bathUpdateOrderItem(@Param("orderItems")List<OrderItemCustom> orderItems) throws SQLException;

批量删除

使用数组接受参数

1
2
3
4
5
6
7
8
@Delete("<script>" +
"DELETE FROM order_items WHERE id IN" +
"<foreach collection=\"ids\" item=\"itemId\" index=\"index\" separator=\",\" open=\"(\" close=\")\">"+
"#{itemId}" +
"</foreach>"+
"</script>"
)
Integer bathdeleteOrderItem(@Param("ids") Integer[] itemIds)throws SQLException;

controller中使用 @RequestParam 注解修饰数组,请求时将参数拼接到url后面(类似Get请求)

SpringBoot-跟踪启动过程

本文基于spring-boot版本2.1.4.RELEASE
首先使用spring-boot-starter-web构建一个web项目,编写代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@RestController
public class RootController {
@GetMapping("/")
public String welcome() {
return "Hello world!";
}
}

入口程序执行的方法SpringApplication.run(Application.class, args)是SpringApplication类的静态run方法

1
2
3
4
5
6
7
8
9
代码摘自:org.springframework.boot.SpringApplication

public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}

第一个静态run函数实际上是将单个的source构造成数组,然后调用了第二个静态run函数。第二个函数创建了SpringApplication对象,并调用该对象的非静态run函数(有三个run函数)

因此,我们也可以将前面程序主类的启动过程修改为:

查看更多