Fork me on GitHub

Spring Boot:传递参数

原文地址:https://dzone.com/articles/spring-boot-passing-parameters

译者:高行行

Spring Boot 通过 Java 注解使传递参数变得容易。

Person kicking soccer ball

请求 URL 时传递参数是 Spring Boot 的最基本功能之一。本文介绍了在请求中传递参数的方法。有两种方法可以传递参数。传递参数的常用方法是在 URL 调用中显式指定参数。格式如下:

1
https://www.youtube.com/watch?v=tuNhqqxgxXQ&t=153s

在上面的 URL 中,有两个参数 v 和 t。要传递参数,请输入”?”。然后,添加参数名称,后跟”=”和参数值。它看起来像这样”?v=value”。要传递另一个参数,请使用”&”,后跟第二个参数名。

传递参数的第二种方法是 path variable。例如:

1
http://www.twitter.com/hashimati

此 URL 中的单词”hashimati”是一个变量。

Spring Boot 通过 Java 注解使传递参数变得容易。让我们看看它在 Spring Boot 中是如何工作的。

创建一个项目

使用Spring Initializer生成 Gradle 或 Maven 项目。你唯一需要的依赖是 Web。

路径变量

在路径变量中,我们在 Web 服务的路径内传递参数。下面的代码段演示了采用路径变量编写 Web 服务的方法:

1
2
3
4
@GetMapping("/hello0/{name}")
public String hello0(@PathVariable("name") String name){
return "Hello " + name;
}
  • name 变量嵌入在服务路径中的大括号之间。
  • name 参数用@PathVariable注释。我们在 @PathVariable 中传递了name来表明方法签名中的name参数存储了{name}值。

Request Parameter

在 Spring Boot 中,有两种方法可以在 URL 请求中传递参数:

  1. 使用 @RequestParam

@RequestParam 可用于注解方法签名中的参数。如以下代码片段所示:

1
2
3
4
@GetMapping("/hello1")
public String hello1(@RequestParam(name="name", required = false, defaultValue = "Ahmed") String name){
return "Hello " + name;
}

@RequestParam 具有以下参数:

  • name:请求参数的名称
  • defaultValue:如果未在请求中传递参数,则为默认值。
  • required:如果为 true,则该参数为必填。否则,不是。
  1. 将参数封装在对象中

使用对象是一种封装参数的好方法。让我们看一下下面的代码片段。首先,我们将创建一个名称为 Params 的类 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Params {
private String a, b;
public void setA(String a) {
this.a = a;
}
public void setB(String b) {
this.b = b;
}
public String getA() {
return a;
}
public String getB() {
return b;
}
}

Params 类中应实现 getter 和 setter 方法 。然后,我们将 Params 在 Web 服务的方法签名中使用

1
2
3
4
5
@GetMapping("/hello2")
public String hello2(Parms parameters){
//implement the setter and getter of the Params class.
return "Hello " + parameters.a + " " + parameters.b;
}

最后,该 Web 服务获取到两个参数,分别是ab

进一步阅读

使用 Spring @RequestMapping 注释

在 Spring 中使用过滤器

如何在CentOS 7上安装Jenkins

原文地址:https://linuxize.com/post/how-to-install-jenkins-on-centos-7/

翻译:高行行

Jenkins是一个基于 Java 的开源自动化服务器,它提供了一种简单的方法来建立持续集成和持续交付(CI / CD)管道。

持续集成(CI)是 DevOps 的一种实践,团队成员定期将其代码更改提交到版本控制存储库,然后运行自动构建和测试。持续交付(CD)是一系列实践,其中代码更改会自动生成,测试并部署到生产中。

本教程将引导你完成使用官方 Jenkins 存储库在 CentOS 7 系统上安装 Jenkins 的步骤。

1. 先决条件

在继续本教程之前,请确保你以具有 sudo 特权的用户身份登录。

2. 安装 Jenkins

要在你的 CentOS 系统上安装 Jenkins,请执行以下步骤:

  1. Jenkins 是 Java 应用程序,因此第一步是安装 Java。运行以下命令以安装 OpenJDK 8 软件包:

    1
    sudo yum install java-1.8.0-openjdk-devel

    当前版本的 Jenkins 尚不支持 Java 10(和 Java 11)。如果你的计算机上安装了多个 Java 版本,请确保 Java 8 是默认 Java 版本

  2. 下一步是启用 Jenkins 存储库。为此,请使用以下curl命令导入 GPG 密钥:

    1
    curl --silent --location http://pkg.jenkins-ci.org/redhat-stable/jenkins.repo | sudo tee /etc/yum.repos.d/jenkins.repo

    并使用以下命令将存储库添加到你的系统中:

    1
    sudo rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key
  3. 启用存储库后,通过输入以下命令安装最新的 Jenkins 稳定版本:

    1
    sudo yum install jenkins

    安装过程完成后,使用以下命令启动 Jenkins 服务:

    1
    sudo systemctl start jenkins

    要检查它是否成功启动,请运行:

    1
    systemctl status jenkins

    你应该看到类似以下内容:

    1
    2
    3
    4
    5
    6
    7
    Output
    ● jenkins.service - LSB: Jenkins Automation Server
    Loaded: loaded (/etc/rc.d/init.d/jenkins; bad; vendor preset: disabled)
    Active: active (running) since Thu 2018-09-20 14:58:21 UTC; 15s ago
    Docs: man:systemd-sysv-generator(8)
    Process: 2367 ExecStart=/etc/rc.d/init.d/jenkins start (code=exited, status=0/SUCCESS)
    CGroup: /system.slice/jenkins.service

    最后,启用 Jenkins 服务以在系统引导时启动。

    1
    sudo systemctl enable jenkins
    1
    2
    3
    Output
    jenkins.service is not a native service, redirecting to /sbin/chkconfig.
    Executing /sbin/chkconfig jenkins on

3. 调整防火墙

如果要在受防火墙保护的远程 CentOS 服务器上安装 Jenkins,则需要开放8080 端口。

使用以下命令打开必要的端口:

1
2
sudo firewall-cmd --permanent --zone=public --add-port=8080/tcp
sudo firewall-cmd --reload

4. 设置 Jenkins

要设置新的 Jenkins 安装,请打开浏览器,然后输入你的域或 IP 地址,然后输入端口 8080

1
http://your_ip_or_domain:8080

将会出现类似以下的屏幕,提示你输入在安装过程中创建的管理员密码:

使用以下命令在终端上打印密码:

1
sudo cat /var/lib/jenkins/secrets/initialAdminPassword

你应该看到一个 32 个字符长的字母数字密码,如下所示:

1
2115173b548f4e99a203ee99a8732a32

从终端复制密码,将其粘贴到“管理员密码”字段中,然后单击Continue

在下一个屏幕上,系统将询问你是否要安装建议的插件或选择特定的插件。单击该Install suggested plugins框,安装过程将立即开始。

安装完成后,将提示你设置第一个管理用户。填写所有必需的信息,然后单击确定Save and Continue

在下一页上,将要求你设置 Jenkins 实例的 URL。URL 字段将填充自动生成的 URL。

要完成设置,请单击Save and Finish按钮确认 URL 。

最后,单击Start using Jenkins按钮,你将被重定向到你在上个步骤创建的管理员用户身份登录的 Jenkins 仪表板。

如果你到了这一步,则说明你已经在 CentOS 系统上成功安装了 Jenkins。

5. 最后

在本教程中,你学习了如何在基于 CentOS / RHEL 的系统上安装并完成 Jenkins 的初始配置。现在,你可以访问Jenkins官方文档页面,并开始探索 Jenkins 的工作流程和插件模块。

java 并发

原作者:CyC2018

原文地址:https://github.com/CyC2018/CS-Notes/blob/master/notes/Java 并发.md

小结:

一、线程状态转换:6种状态

  1. 新建(new):Thread t = new Thread(r) 时
  2. 可运行(Runnable):t.start(),一旦调用 start 方法 。 线程处于 runnable 状态。 一个可运行的线程可能正在运行也可能没有运行 。
  3. 阻塞(Blocked):等待获取一个排它锁,如果其线程释放了锁就会结束此状态。
  4. 无限期等待(Waiting):等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。
  5. 限期等待(Timed Waiting):无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
  6. 死亡(Terminated):线程因如下两个原因之一而被终止,因为 run 方法正常退出而自然死亡,因为一个没有捕获的异常终止了 run 方法而意外死亡 。

二、使用线程

  • 实现 Runnable 接口:实现 Runnable 接口,重写 run() 方法,通过 Thread 调用 start() 方法来启动线程。
  • 实现 Callable 接口:与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
  • 继承 Thread 类:继承 Thread 类,重写 run() 方法,调用 start() 方法。
  • 实现接口 VS 继承 Thread:实现接口会更好一些

三、基础线程机制

  1. Executor:Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。
    • CachedThreadPool:一个任务创建一个线程;
    • FixedThreadPool:所有任务只能使用固定大小的线程;
    • SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool。
  2. Daemon:守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
  3. sleep():Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒。sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。
  4. yield():对静态方法 Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。

四、中断

  • InterruptedException:通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。
  • interrupted():使用 interrupted() 方法来判断线程是否处于中断状态。
  • Executor 的中断操作:调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。

如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future<?> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。

五、互斥同步:Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。

  • synchronized:

    • 同步一个代码块:它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。
  • 同步一个方法:它和同步代码块一样,作用于同一个对象。

    • 同步一个类:作用于整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步。
    • 同步一个静态方法:作用于整个类。
  • ReentrantLock:ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁。

  • 比较:

    • 锁的实现:synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。
  • 性能:新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。

  • 等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。ReentrantLock 可中断,而 synchronized 不行。

  • 公平锁:公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。

  • 锁绑定多个条件:一个 ReentrantLock 可以同时绑定多个 Condition 对象。

    • 使用选择:除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。

六、线程之间的协作

  • join():在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。

  • wait() notify() notifyAll():调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。wait() 会释放锁。

  • await() signal() signalAll():java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。

相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。

七、J.U.C - AQS

  • CountDownLatch:用来控制一个或者多个线程等待多个线程。维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒。
  • CyclicBarrier
  • Semaphore

八、J.U.C - 其它组件

  • FutureTask:FutureTask 实现了 RunnableFuture 接口,该接口继承自 Runnable 和 Future 接口,这使得 FutureTask 既可以当做一个任务执行,也可以有返回值。
  • BlockingQueue:阻塞队列,使用 BlockingQueue 可以实现生产者消费者问题
  • ForkJoin:主要用于并行计算中,和 MapReduce 原理类似,都是把大的计算任务拆分成多个小任务并行计算。

九、线程不安全示例

十、Java 内存模型

  • 主内存与工作内存
  • 内存间交互操作
    • read:把一个变量的值从主内存传输到工作内存中
    • load:在 read 之后执行,把 read 得到的值放入工作内存的变量副本中
      • use:把工作内存中一个变量的值传递给执行引擎
    • assign:把一个从执行引擎接收到的值赋给工作内存的变量
    • store:把工作内存的一个变量的值传送到主内存中
    • write:在 store 之后执行,把 store 得到的值放入主内存的变量中
    • lock:作用于主内存的变量
    • unlock
  • 内存模型三大特性
    • 原子性:Java 内存模型保证了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性
    • 可见性:可见性指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性的。主要有三种实现可见性的方式:
      • volatile
      • synchronized,对一个变量执行 unlock 操作之前,必须把变量值同步回主内存。
      • final,被 final 关键字修饰的字段在构造器中一旦初始化完成,并且没有发生 this 逃逸(其它线程通过 this 引用访问到初始化了一半的对象),那么其它线程就能看见 final 字段的值。
    • 有序性:有序性是指在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
  • 先行发生原则:上面提到了可以用 volatile 和 synchronized 来保证有序性。除此之外,JVM 还规定了先行发生原则,让一个操作无需控制就能先于另一个操作完成。
    • 单一线程原则
    • 管程锁定原则
    • volatile 变量规则
    • 线程启动规则
    • 线程加入规则:Thread 对象的结束先行发生于 join() 方法返回。
    • 线程中断规则
    • 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。
    • 传递性:如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。

十一、线程安全:多个线程不管以何种方式访问某个类,并且在主调代码中不需要进行同步,都能表现正确的行为。线程安全有以下几种实现方式:

  • 不可变:不可变(Immutable)的对象一定是线程安全的,不需要再采取任何的线程安全保障措施。不可变的类型有以下这些:
    • final 关键字修饰的基本数据类型
    • String
    • 枚举类型
    • Number 部分子类,如 Long 和 Double 等数值包装类型,BigInteger 和 BigDecimal 等大数据类型。但同为 Number 的原子类 AtomicInteger 和 AtomicLong 则是可变的。
  • 互斥同步:synchronized 和 ReentrantLock。悲观锁
  • 非阻塞同步:先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。
    • CAS
    • AtomicInteger:J.U.C 包里面的整数原子类 AtomicInteger 的方法调用了 Unsafe 类的 CAS 操作。
    • ABA:如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。
  • 无同步方案
    • 栈封闭:多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。
    • 线程本地存储(Thread Local Storage)
    • 可重入代码(Reentrant Code)

十二、锁优化

  • 自旋锁:获取锁时自旋一段时间,看能拿到锁不。只适用于共享数据的锁定状态很短的场景。
  • 锁消除:锁消除是指对于被检测出不可能存在竞争的共享数据的锁进行消除。
  • 锁粗化:如果虚拟机探测到一串零碎的操作都对同一个对象加锁,将会把加锁的范围扩展(粗化)到整个操作序列的外部。
  • 轻量级锁
  • 偏向锁

十三、多线程开发良好的实践

一、线程状态转换


![](https://raw.githubusercontent.com/gaohanghang/images/master/img/20191204204337.png)

1.1 新建(New)

创建后尚未启动。

1.2 可运行(Runnable)

可能正在运行,也可能正在等待 CPU 时间片。

包含了操作系统线程状态中的 Running 和 Ready。

1.3 阻塞(Blocked)

等待获取一个排它锁,如果其线程释放了锁就会结束此状态。

1.4 无限期等待(Waiting)

等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。

进入方法 退出方法
没有设置 Timeout 参数的 Object.wait() 方法 Object.notify() / Object.notifyAll()
没有设置 Timeout 参数的 Thread.join() 方法 被调用的线程执行完毕
LockSupport.park() 方法 LockSupport.unpark(Thread)

1.5 限期等待(Timed Waiting)

无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。

调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。

调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。

睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。

阻塞和等待的区别在于,阻塞是被动的,它是在等待获取一个排它锁。而等待是主动的,通过调用 Thread.sleep() 和 Object.wait() 等方法进入。

进入方法 退出方法
Thread.sleep() 方法 时间结束
设置了 Timeout 参数的 Object.wait() 方法 时间结束 / Object.notify() / Object.notifyAll()
设置了 Timeout 参数的 Thread.join() 方法 时间结束 / 被调用的线程执行完毕
LockSupport.parkNanos() 方法 LockSupport.unpark(Thread)
LockSupport.parkUntil() 方法 LockSupport.unpark(Thread)

1.5 死亡(Terminated)

可以是线程结束任务之后自己结束,或者产生了异常而结束。

二、使用线程

有三种使用线程的方法:

  • 实现 Runnable 接口;
  • 实现 Callable 接口;
  • 继承 Thread 类。

实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。

实现 Runnable 接口

需要实现 run() 方法。

通过 Thread 调用 start() 方法来启动线程。

1
2
3
4
5
public class MyRunnable implements Runnable {
public void run() {
// ...
}
}
1
2
3
4
5
public static void main(String[] args) {
MyRunnable instance = new MyRunnable();
Thread thread = new Thread(instance);
thread.start();
}

2.2 实现 Callable 接口

与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。

1
2
3
4
5
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}
1
2
3
4
5
6
7
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}

2.3 继承 Thread 类

同样也是需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。

当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。

1
2
3
4
5
public class MyThread extends Thread {
public void run() {
// ...
}
}
1
2
3
4
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}

2.4 实现接口 VS 继承 Thread

实现接口会更好一些,因为:

  • Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
  • 类可能只要求可执行就行,继承整个 Thread 类开销过大。

三、基础线程机制

3.1 Executor

Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。

主要有三种 Executor:

  • CachedThreadPool:一个任务创建一个线程;
  • FixedThreadPool:所有任务只能使用固定大小的线程;
  • SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool。
1
2
3
4
5
6
7
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executorService.execute(new MyRunnable());
}
executorService.shutdown();
}

3.2 Daemon

守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。

当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。

main() 属于非守护线程。

在线程启动之前使用 setDaemon() 方法可以将一个线程设置为守护线程。

1
2
3
4
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.setDaemon(true);
}

3.3 sleep()

Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒。

sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。

1
2
3
4
5
6
7
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

3.4 yield()

对静态方法 Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。

1
2
3
public void run() {
Thread.yield();
}

四、中断

一个线程执行完毕之后会自动结束,如果在运行过程中发生异常也会提前结束。

4.1 InterruptedException

通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。

对于以下代码,在 main() 中启动一个线程之后再中断它,由于线程中调用了 Thread.sleep() 方法,因此会抛出一个 InterruptedException,从而提前结束线程,不执行之后的语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class InterruptExample {

private static class MyThread1 extends Thread {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("Thread run");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1
2
3
4
5
6
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new MyThread1();
thread1.start();
thread1.interrupt();
System.out.println("Main run");
}
1
2
3
4
5
6
Main run
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at InterruptExample.lambda$main$0(InterruptExample.java:5)
at InterruptExample$$Lambda$1/713338599.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)

4.2 interrupted()

如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。

但是调用 interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。

1
2
3
4
5
6
7
8
9
10
11
12
public class InterruptExample {

private static class MyThread2 extends Thread {
@Override
public void run() {
while (!interrupted()) {
// ..
}
System.out.println("Thread end");
}
}
}
1
2
3
4
5
public static void main(String[] args) throws InterruptedException {
Thread thread2 = new MyThread2();
thread2.start();
thread2.interrupt();
}
1
Thread end

4.3 Executor 的中断操作

调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。

以下使用 Lambda 创建线程,相当于创建了一个匿名内部线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
try {
Thread.sleep(2000);
System.out.println("Thread run");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
executorService.shutdownNow();
System.out.println("Main run");
}
1
2
3
4
5
6
7
8
Main run
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at ExecutorInterruptExample.lambda$main$0(ExecutorInterruptExample.java:9)
at ExecutorInterruptExample$$Lambda$1/1160460865.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future<?> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。

1
2
3
4
Future<?> future = executorService.submit(() -> {
// ..
});
future.cancel(true);

五、互斥同步

Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。

5.1 synchronized

1. 同步一个代码块

1
2
3
4
5
public void func() {
synchronized (this) {
// ...
}
}

它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。

对于以下代码,使用 ExecutorService 执行了两个线程,由于调用的是同一个对象的同步代码块,因此这两个线程会进行同步,当一个线程进入同步语句块时,另一个线程就必须等待。

阅读更多...

微服务中如何使用RestTemplate优雅调用API(拦截器、异常处理、消息转换)

原文地址:https://mp.weixin.qq.com/s/DoW_wlVu3_aWx-bTnSuOWg

小结:

  1. 自定义 interceptor 拦截器:可以用于记录 resttemplate 请求和响应的信息,以及统一 head 的设置。

  2. 自定义异常处理:添加自定义错误处理的最简单策略是将调用包装在 try/catch 代码块中。然后,我们根据需要处理捕获的异常。但是,随着远程 API 或调用数量的增加,这种简单的策略无法很好地扩展。通过自定义异常处理可以为所有远程调用实现可重用的错误处理程序,效率会更高。– https://www.baeldung.com/spring-rest-template-error-handling

  3. 自定义 message 转化器

在微服务中,rest 服务互相调用是很普遍的,我们该如何优雅地调用? 其实在 Spring 框架使用 RestTemplate 类可以优雅地进行 rest 服务互相调用,它简化了与 http 服务的通信方式,统一了 RESTful 的标准,封装了 http 连接,操作使用简便,还可以自定义 RestTemplate 所需的模式。其中:

  • RestTemplate 默认使用HttpMessageConverter实例将 HTTP 消息转换成 POJO 或者从 POJO 转换成 HTTP 消息。默认情况下会注册主 mime 类型的转换器,但也可以通过 setMessageConverters 注册自定义转换器。
  • RestTemplate 使用了默认的 DefaultResponseErrorHandler,对 40X Bad Request 或 50X internal 异常 error 等错误信息捕捉。
  • RestTemplate 还可以使用拦截器 interceptor,进行对请求链接跟踪,以及统一 head 的设置。

其中,RestTemplate 还定义了很多的 REST 资源交互的方法,其中的大多数都对应于 HTTP 的方法,如下:

方法 解析
delete() 在特定的 URL 上对资源执行 HTTP DELETE 操作
exchange() 在 URL 上执行特定的 HTTP 方法,返回包含对象的 ResponseEntity
execute() 在 URL 上执行特定的 HTTP 方法,返回一个从响应体映射得到的对象
getForEntity() 发送一个 HTTP GET 请求,返回的 ResponseEntity 包含了响应体所映射成的对象
getForObject() 发送一个 HTTP GET 请求,返回的请求体将映射为一个对象
postForEntity() POST 数据到一个 URL,返回包含一个对象的 ResponseEntity
postForObject() POST 数据到一个 URL,返回根据响应体匹配形成的对象
headForHeaders() 发送 HTTP HEAD 请求,返回包含特定资源 URL 的 HTTP 头
optionsForAllow() 发送 HTTP OPTIONS 请求,返回对特定 URL 的 Allow 头信息
postForLocation() POST 数据到一个 URL,返回新创建资源的 URL
put() PUT 资源到特定的 URL

1. springboot 集成 RestTemplate

在项目中可以通过增加自定义的异常处理、MessageConverter以及拦截器interceptor对 RestTemplate 进行优雅地使用,详情请查看接下来的内容。

1.1 导入依赖:(RestTemplate 集成在 Web Start 中)

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>

1.2 RestTemplat 配置:

  • 使用ClientHttpRequestFactory属性配置 RestTemplat 参数,比如 ConnectTimeout,ReadTimeout;
  • 增加自定义的interceptor拦截器和异常处理;
  • 追加message转换器;
  • 配置自定义的异常处理.
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
@Configuration
public class RestTemplateConfig {

@Value("${resttemplate.connection.timeout}")
private int restTemplateConnectionTimeout;
@Value("${resttemplate.read.timeout}")
private int restTemplateReadTimeout;

@Bean
//@LoadBalanced
public RestTemplate restTemplate( ClientHttpRequestFactory simleClientHttpRequestFactory) {
RestTemplate restTemplate = new RestTemplate();
//配置自定义的message转换器
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
messageConverters.add(new CustomMappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);
//配置自定义的interceptor拦截器
List<ClientHttpRequestInterceptor> interceptors=new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new HeadClientHttpRequestInterceptor());
interceptors.add(new TrackLogClientHttpRequestInterceptor());
restTemplate.setInterceptors(interceptors);
//配置自定义的异常处理
restTemplate.setErrorHandler(new CustomResponseErrorHandler());
restTemplate.setRequestFactory(simleClientHttpRequestFactory);

return restTemplate;
}

@Bean
public ClientHttpRequestFactory simleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory reqFactory= new SimpleClientHttpRequestFactory();
reqFactory.setConnectTimeout(restTemplateConnectionTimeout);
reqFactory.setReadTimeout(restTemplateReadTimeout);
return reqFactory;
}
}

1.3 组件(自定义异常处理、interceptor 拦截器、message 转化器)

自定义interceptor拦截器,实现ClientHttpRequestInterceptor接口

  • 自定义TrackLogClientHttpRequestInterceptor,记录resttemplaterequestresponse信息,可进行追踪分析;
  • 自定义HeadClientHttpRequestInterceptor,设置请求头的参数。API 发送各种请求,很多请求都需要用到相似或者相同的 Http Header。如果在每次请求之前都把Header填入HttpEntity/RequestEntity,这样的代码会显得十分冗余,可以在拦截器统一设置。

1.3.1 自定义 interceptor 拦截器

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
/**
* @Auther: ccww
* @Date: 2019/10/25 22:48,记录resttemplate访问信息
* @Description: 记录resttemplate访问信息
*/
@Slf4j
public class TrackLogClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
trackRequest(request,body);
ClientHttpResponse httpResponse = execution.execute(request, body);
trackResponse(httpResponse);
return httpResponse;
}

private void trackResponse(ClientHttpResponse httpResponse)throws IOException {
log.info("============================response begin==========================================");
log.info("Status code : {}", httpResponse.getStatusCode());
log.info("Status text : {}", httpResponse.getStatusText());
log.info("Headers : {}", httpResponse.getHeaders());
log.info("=======================response end=================================================");
}

private void trackRequest(HttpRequest request, byte[] body)throws UnsupportedEncodingException {
log.info("======= request begin ========");
log.info("uri : {}", request.getURI());
log.info("method : {}", request.getMethod());
log.info("headers : {}", request.getHeaders());
log.info("request body : {}", new String(body, "UTF-8"));
log.info("======= request end ========");
}
}

HeadClientHttpRequestInterceptor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Slf4j
public class HeadClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
log.info("#####head handle########");
HttpHeaders headers = httpRequest.getHeaders();
headers.add("Accept", "application/json");
headers.add("Accept-Encoding", "gzip");
headers.add("Content-Encoding", "UTF-8");
headers.add("Content-Type", "application/json; charset=UTF-8");
ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);
HttpHeaders headersResponse = response.getHeaders();
headersResponse.add("Accept", "application/json");
return response;
}
}

1.3.2 自定义异常处理

自定义异常处理,可继承DefaultResponseErrorHandler或者实现ResponseErrorHandler接口:

  • 实现自定义ErrorHandler的思路是根据响应消息体进行相应的异常处理策略,对于其他异常情况由父类DefaultResponseErrorHandler来进行处理。
  • 自定义CustomResponseErrorHandler进行 30x 异常处理
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
/**
* @Auther: Ccww
* @Date: 2019/10/28 17:00
* @Description: 30X的异常处理
*/
@Slf4j
public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
HttpStatus statusCode = response.getStatusCode();
if(statusCode.is3xxRedirection()){
return true;
}
return super.hasError(response);
}

@Override
public void handleError(ClientHttpResponse response) throws IOException {
HttpStatus statusCode = response.getStatusCode();
if(statusCode.is3xxRedirection()){
log.info("########30X错误,需要重定向!##########");
return;
}
super.handleError(response);
}

}

1.3.3 自定义 message 转化器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @Auther: Ccww
* @Date: 2019/10/29 21:15
* @Description: 将Content-Type:"text/html"转换为Map类型格式
*/
public class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
public CustomMappingJackson2HttpMessageConverter() {
List<MediaType> mediaTypes = new ArrayList<MediaType>();
mediaTypes.add(MediaType.TEXT_PLAIN);
mediaTypes.add(MediaType.TEXT_HTML); //加入text/html类型的支持
setSupportedMediaTypes(mediaTypes);// tag6
}

}

2. RestTemplate 源码解析

2.1 默认调用链路

restTemplate进行 API 调用时,默认调用链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
###########1.使用createRequest创建请求########
resttemplate->execute()->doExecute()
HttpAccessor->createRequest()
//获取拦截器Interceptor,InterceptingClientHttpRequestFactory,SimpleClientHttpRequestFactory
InterceptingHttpAccessor->getRequestFactory()
//获取默认的SimpleBufferingClientHttpRequest
SimpleClientHttpRequestFactory->createRequest()

#######2.获取响应response进行处理###########
AbstractClientHttpRequest->execute()->executeInternal()
AbstractBufferingClientHttpRequest->executeInternal()

###########3.异常处理#####################
resttemplate->handleResponse()

##########4.响应消息体封装为java对象#######
HttpMessageConverterExtractor->extractData()

2.2 restTemplate->doExecute()

在默认调用链中,**restTemplate** 进行 API 调用都会调用 doExecute 方法,此方法主要可以进行如下步骤:

1)使用createRequest创建请求,获取响应

2)判断响应是否异常,处理异常

3)将响应消息体封装为 java 对象

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
@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {

Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
//使用createRequest创建请求
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
//获取响应response进行处理
response = request.execute();
//异常处理
handleResponse(url, method, response);
//响应消息体封装为java对象
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}catch (IOException ex) {
String resource = url.toString();
String query = url.getRawQuery();
resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
}finally {
if (response != null) {
response.close();
}
}
}

2.3 InterceptingHttpAccessor->getRequestFactory()

在默认调用链中,InterceptingHttpAccessor的getRequestFactory()方法中,如果没有设置interceptor拦截器,就返回默认的SimpleClientHttpRequestFactory,反之,返回InterceptingClientHttpRequestFactoryrequestFactory,可以通过resttemplate.setInterceptors设置自定义拦截器interceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Return the request factory that this accessor uses for obtaining client request handles.
public ClientHttpRequestFactory getRequestFactory() {
//获取拦截器interceptor(自定义的)
List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
if (!CollectionUtils.isEmpty(interceptors)) {
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
}
else {
return super.getRequestFactory();
}
}

然后再调用SimpleClientHttpRequestFactory的createRequest创建连接:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
prepareConnection(connection, httpMethod.name());

if (this.bufferRequestBody) {
return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
}
else {
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
}
}

2.4 resttemplate->handleResponse()

在默认调用链中,resttemplate的handleResponse,响应处理,包括异常处理,而且异常处理可以通过调用setErrorHandler方法设置自定义的ErrorHandler,实现对请求响应异常的判别和处理。自定义的ErrorHandler需实现ResponseErrorHandler接口,同时Spring boot也提供了默认实现DefaultResponseErrorHandler,因此也可以通过继承该类来实现自己的ErrorHandler

DefaultResponseErrorHandler默认对 40X Bad Request或 50X internal异常error等错误信息捕捉。如果想捕捉服务本身抛出的异常信息,需要通过自行实现RestTemplateErrorHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ResponseErrorHandler errorHandler = getErrorHandler();
//判断响应是否有异常
boolean hasError = errorHandler.hasError(response);
if (logger.isDebugEnabled()) {
try {
int code = response.getRawStatusCode();
HttpStatus status = HttpStatus.resolve(code);
logger.debug("Response " + (status != null ? status : code));
}catch (IOException ex) {
// ignore
}
}
//有异常进行异常处理
if (hasError) {
errorHandler.handleError(url, method, response);
}
}

2.5 HttpMessageConverterExtractor->extractData()

在默认调用链中, HttpMessageConverterExtractorextractData中进行响应消息体封装为java对象,就需要使用message转换器,可以通过追加的方式增加自定义的messageConverter:先获取现有的messageConverter,再将自定义的messageConverter添加进去。

根据restTemplatesetMessageConverters的源码可得,使用追加的方式可防止原有的messageConverter丢失,源码:

1
2
3
4
5
6
7
8
9
10
11
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
//检验
validateConverters(messageConverters);
// Take getMessageConverters() List as-is when passed in here
if (this.messageConverters != messageConverters) {
//先清除原有的messageConverter
this.messageConverters.clear();
//后加载重新定义的messageConverter
this.messageConverters.addAll(messageConverters);
}
}

HttpMessageConverterExtractor的extractData源码:

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
MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
return null;
}
//获取到response的ContentType类型
MediaType contentType = getContentType(responseWrapper);

try {
//依次循环messageConverter进行判断是否符合转换条件,进行转换java对象
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
//会根据设置的返回类型responseType和contentType参数进行匹配,选择合适的MessageConverter
if (messageConverter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<?> genericMessageConverter =
(GenericHttpMessageConverter<?>) messageConverter;
if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
if (logger.isDebugEnabled()) {
ResolvableType resolvableType = ResolvableType.forType(this.responseType);
logger.debug("Reading to [" + resolvableType + "]");
}
return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
}
}
if (this.responseClass != null) {
if (messageConverter.canRead(this.responseClass, contentType)) {
if (logger.isDebugEnabled()) {
String className = this.responseClass.getName();
logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");
}
return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
}
}
}
}
.....
}

2.6 contentType 与 messageConverter 之间的关系

HttpMessageConverterExtractorextractData方法中看出,会根据contentTyperesponseClass选择messageConverter是否可读、消息转换。关系如下:

类名 支持的 JavaType 支持的 MediaType
ByteArrayHttpMessageConverter byte[] application/octet-stream, /
StringHttpMessageConverter String text/plain, /
ResourceHttpMessageConverter Resource /
SourceHttpMessageConverter Source application/xml, text/xml, application/*+xml
AllEncompassingFormHttpMessageConverter Map<K, List<?>> application/x-www-form-urlencoded, multipart/form-data
MappingJackson2HttpMessageConverter Object application/json, application/*+json
Jaxb2RootElementHttpMessageConverter Object application/xml, text/xml, application/*+xml
JavaSerializationConverter Serializable x-java-serialization;charset=UTF-8
FastJsonHttpMessageConverter Object /

ARTS-10

ARTS是由左耳朵耗子陈皓在极客时间专栏《左耳听风》中发起的一个每周学习打卡计划。

1
2
3
4
5
6
7
Algorithm:至少做一个 LeetCode 的算法题。主要为了编程训练和学习。

Review:阅读并点评至少一篇英文技术文章。主要为了学习英文,如果你英文不行,很难成为技术高手。

Tip:学习至少一个技术技巧。主要是为了总结和归纳你日常工作中所遇到的知识点。

Share:分享一篇有观点和思考的技术文章。主要为了输出你的影响力,能够输出你的价值观。

Algorithm(算法)

无序数组中位数

参考文章:https://www.cnblogs.com/shizhh/p/5746151.html

1

Review(点评)

为什么现代网络中需要JSON Web令牌(JWT)?

总结:

  • HTTP协议是无状态的,这意味着一个新请求对上一个请求一无所知
  • 服务器端会话是HTTP无状态性的一种解决方案,但是从长远来看,这些对我们的扩展能力构成了威胁。
  • JWT是独立的,这意味着它包含允许或拒绝对API的任何给定请求所需的所有信息
  • JWT在设计上是无状态的,因此我们不必与HTTP的无状态设计作斗争
  • JWT是经过编码的,而不是加密的

Tip(技巧)

Share(分享)

慕课网 ThreadLocal 教学视频学习笔记

思考 12 人人都是自媒体

参考文章:革新的知识获取方式 — 背后的秘密!

这是一个人人都可以成为自媒体的时代,也是人人都应该成为自媒体的时代。

博客算自媒体1.0

微博算自媒体2.0

Vlog算自媒体3.0

我觉得通过输出来学习应该会成为下一个主流的方式,通过不断输出来校验检测自己,不断完善和提高自己。

我觉得如果一个人能够有意识的成为生产方时,他的思考方式就已经发生了巨大的变化。

去成为知识的生产者而不要成为被动接收者。

IntelliJ IDEA 2019.3 发布,更好的性能和易用性

原文地址:https://www.jetbrains.com/idea/whatsnew

参考文章:

https://www.oschina.net/news/111709/intellij-idea-2019-3-released

https://blog.csdn.net/hollis_chuang/article/details/101186271

IntelliJ IDEA 在 2019 年 11 月 28 日推出了今年最终主要版本-IntelliJ IDEA 2019.3!

这里有个小知识,就是 2019.1、2019.2、2019.3 这些点后面的数字表示的不是月份,而是这一年的第几个版本。

以下为翻译内容,翻译的可能不太准确,之后将会对翻译错误的地方进行修改,由于公众号文章发出后就不能进行大的修改,因此修改后的文章将会更新到以下链接地址,之后也会再发布一篇错误修改后的公众号文章,欢迎关注我。

文章更新链接:https://www.yuque.com/gaohanghang/og37t5/hry6a6


2019.3 11 月 28

IntelliJ IDEA 2019.3 提供了重大的性能和可用性改进,包括更快的启动,主题和快捷键插件的安装更容易,增进 VCS workflows,并增加了对微服务框架、MongoDB 等的支持。

1. 更好的性能

1.1 更快的启动

此版本主要性能改进之一是启动时间比以往任何时候都短。我们采用了重大的体系结构更改,来并行化 IDE 在启动时执行的某些任务,以便它们不会顺序执行。

IntelliJ IDEA启动速度

1.2 减少内存消耗

在 IntelliJ IDEA 2019.3 中,我们优化了性能以减少导入大型 Gradle 项目时的峰值内存消耗。

Gradle导入时各阶段内存使用情况

1.3 响应式用户界面

我们已经解决了提交给我们的 1600 多个 UI 冻结报告。除此之外,我们还修复了在 Maven 项目中编辑 POM.xml 文件的问题,现在可以立即显示补全建议,而不会出现任何延迟。其他修复有可以更快地处理大型项目中的 VCS 状态更新,更好地处理 ignored 文件,更快地渲染项目树,在使用大量编辑或 debugger 标签页时具有更好的性能,并提高了速度。

1.4 更好的 Java 性能

此版本对 Java 类型推断进行了许多改进,不仅修复了各种编辑器冻结问题,而且还为长的方法调用链加快了 Java 类型推断。当应用于 multiple lines 时,’Join Lines‘ 操作可以更快地工作。你还将体验到 Java 代码的加速高亮,尤其是涉及到带有通用 var-args 的方法时(其中放置了数十个参数)。

1.5 更好的 Kotlin 性能

IntelliJ IDEA 2019.3 捆绑了 Kotlin 1.3.60,提供了令人印象深刻的加速效果,例如在编辑器中更快地高亮。

2. 改善的可用性

2.1 在不重新启动 IDE 的情况下安装主题和快捷键插件

在 IntelliJ IDEA 2019.3 中,我们实现了对动态插件安装的支持,这意味着现在你可以安装和卸载主题和快捷键插件,而无需重新启动 IDE。我们计划在将来的版本中将此方法扩展到大多数插件,以使插件管理尽可能容易。

2.2 更明显的滚动条

如果你在查看滚动条时遇到困难,现在可以让它脱颖而出。只需启用 “Settings/Preferences | Appearance & Behavior | Appearance” 下的 ‘Use contrast scrollbars’ 设置即可。

2.3 平滑滚动

我们所做的一个很小但非常有价值的改进是滚动,使用鼠标滚轮滚动后现在可以变得更加平滑。

演示视频地址:https://www.youtube.com/watch?v=MoVS6HOdeew&feature=emb_logo

2.4 自动配置导入的 Maven 或 sbt 项目

现在,当你导入,创建或打开 sbt 或 Maven 项目时,IntelliJ IDEA 会自动为你设置它,因此你不再需要手动配置设置。

2.5 重做上下文动作的行为

在“意图动作”对话框中,即使选择了某个动作并关闭了对话框,IDE 现在仍默认显示所有可用的意图动作。

3. 功能更新

3.1 对 Java 13 功能的扩展支持

我们扩展了对 Java 13 文本块的支持:插入第三个引号时,它们会自动格式化。

Java13 文本块

3.2 可以注入更多模板语言

借助 IntelliJ IDEA 2019.3,你可以向代码中注入更多模板语言,特别是 Pug(ex-Jade),Handlebars,EJS 和 Slim。

3.3 新的 “字段调用树” 操作

现在,通过使用 Ctrl + Alt + H 调用新的“字段调用树”操作,可以轻松地查看所选字段的调用树。

3.4 统一弹出错误和文档

现在,检查中将鼠标悬停在突出显示的符号上时,除了显示代码参考信息之外,你还可以使弹出窗口显示错误。要启用此功能,请在 Settings/Preferences | Editor | General 中选择 ‘Show quick documentation on mouse move’。

3.5 改进 Git checkout 工作流程

为了消除混淆,在远程分支上调用新重命名的 ‘Checkout’ 操作现在将创建一个新的本地分支,将其签出,并将跟踪设置为远程分支。我们还为本地和远程分支添加了 ‘New Branch from Selected’ 操作,该操作创建了一个新的本地分支并将其签出,但未将跟踪设置为任何远程分支。

3.6 推分支的更简单方法

你会很高兴的发现另一个节省时间的方法是,你不再需要签出分支来推送它–你只需在 Git 分支弹出窗口中选择一个分支并将其从那里推送即可。

3.7 统一的“克隆”体验

我们重新设计了 ‘Clone’ 对话框来统一用户界面,用于从不同的 VCS 托管服务获取项目。如果你已经登录到 VCS,IDE 将显示可供选择的可用仓库列表,因此你不再需要输入仓库 URL。

2.8 深入了解合并冲突

现在,如果在 merge,rebase 或 cherry-pick 操作期间发生冲突,在合并对话框中你可以获取有关更改源的更多信息。只需单击 ‘Show Details’ 链接即可获取导致代码冲突的提交列表。

解决冲突时通过 Show Details 查看更多信息

4. 新框架和新技术

4.1 微服务框架支持

为了使你的 IntelliJ IDEA 项目在技术上保持相关性,版本 2019.3 添加了对 Micronaut,Quarkus 和 Helidon 的初步支持。如果你的 Java 项目采用了基于微服务的架构,则可以享受完整的编码协助、导航、检查、查找和其他好处。

4.2 OpenAPI 和 Swagger 支持

我们引入了对 Swagger v2 和 OpenAPI v2 的支持,该支持提供了 schema validation,以及代码补全、导航、查找,以及包含 API 描述的 YAML / JSON 文件中的重命名重构。

4.3 新 Endpoints 窗口

全新的 ‘Endpoints’ 工具窗口提供了项目中用于 HTTP 和 Web Socket 协议的客户端和服务端 API 的聚合视图。此外,你可以将窗口扩展到 IntelliJ IDEA 中当前打开的所有项目。

4.4 Spring Web Flux 支持

在 Spring tool 窗口的 MVC 视图中查看 Web Flux URL 路径的完整列表,在它们之间导航,并受益于编码辅助、搜索以及 URL 和 URLs 的重命名重构。

NewSpringWebFlux

4.5 Java HTTP 客户端支持

IntelliJ IDEA 2019.3 在以下 Java HTTP 客户端 API 中提供 URL 支持: java.net.{URI/URL}, Retrofit v2, OkHttp v3, and Injectable URL reference。你现在在 Java 客户端中可以使用 Java 中常用的所有内容 - 编码辅助,导航,查找。

4.6 Project Reactor 支持

利用对 Java 和 Kotlin Reactor 项目检查的优势,它们会报告在不应阻塞线程的代码片段中检测到的线程阻塞方法调用。IDE 还将警告你有关 Flux 和 Mono 方法的 lambda 运算符可能返回 null 的信息。最重要的是,有一个专用的 Reactor 调试模式,可提供有关响应栈帧和中间变量值的有用视图。

4.7 MongoDB 支持

IntelliJ IDEA 2019.3 附带了期待已久的 MongoDB 支持。添加 MongoDB 数据源后,请在数据库浏览器中查看集合和字段,运行查询并查看查询结果。我们将在将来的版本中扩展对 MongoDB 的支持。

5. 修复了什么

  • IDE 现在可以检测到使用 Homebrew 安装的 Gradle 目录。
  • 用户界面得到了改进,因此现在可以更轻松地手动设置 Gradle 主目录。
  • IntelliJ IDEA 现在支持在 Linux 上通过 KWallet 存储密码。
  • 当用户使用 JavaFX 项目时,IDE 可以在 ‘Scene Builder’ 选项卡中显示带有嵌入式 Scene Builder 的 FXML 文件。
  • 当无法访问 SVN 服务器时,SVN 的“需要身份验证”对话框不再弹出。
  • 我们已经修复了与使用 rebase 执行’git update’操作有关的问题。
  • 现在,在索引 Git 日志时,IDE 将显示一个进度条。
  • 现在,你可以选择是否要在文件历史记录窗口和 VCS 批注中查看更改的创建时间或提交时间。

java并发教程翻译

原文地址:http://tutorials.jenkov.com/java-concurrency/index.html

参考文章:

并发译文

https://blog.csdn.net/JackLang/article/details/52294629

https://www.codeboot.net/tutorials/java-concurrency/index/

该英文教程更新了,因此站在前人的肩膀上,斗胆进行舔砖加瓦。英文不是特别好,全靠 Google 翻译和前辈的博客 🤣

译者:高行行

该 java 并发教程汇总和修订地址:https://www.yuque.com/gaohanghang/og37t5/kqzsra

Java 并发 是一个涵盖 Java 平台多线程,并发和并行的术语。其中包括 Java 并发工具、问题和解决方案。该 Java 并发教程涵盖了多线程的核心概念、并发构造·、并发问题、坏处以及与 Java 中多线程相关的好处。

1. Java 并发和多线程教程

小结:

  1. 什么是多线程:一个程序同时执行多个任务

  2. 为什么要使用多线程:

    • 更好地利用单个 CPU。
    • 更好地利用多个 CPU 或 CPU 内核。
    • 通过响应性获得更好的用户体验。
    • 通过公平地共享计算机资源获得更好的用户体验。
  3. 多线程与多任务:多任务是指在同一刻运行多个程序的能力,多线程程序在较低的层次上扩展了多任务的概念:一个程序同时执行多个任务。通常, 每一个任务称为一个线程 ( thread ) , 它是线程控制的简称。–《java核心技术卷一》

  4. 多线程难点:控制线程访问共享资源

1.1 什么是多线程?

多线程意味着你在同一应用程序中具有多个执行线程。一个线程就像执行应用程序的独立 CPU。因此,多线程应用程序就像具有多个 CPU 同时执行代码不同部分的应用程序。

在其中执行两个线程的应用程序

线程不等于 CPU。通常,单个 CPU 将在多个线程之间共享其执行时间,并在给定的时间量内在每个线程的执行之间进行切换。也可以让应用程序的线程由不同的 CPU 执行。

具有线程由不同线程执行的多个应用程序

1.2 为什么要使用多线程?

为什么要在应用程序中使用多线程有几个原因。使用多线程的一些最常见原因是:

  • 更好地利用单个 CPU。
  • 更好地利用多个 CPU 或 CPU 内核。
  • 通过响应性获得更好的用户体验。
  • 通过公平地共享计算机资源获得更好的用户体验。

我将在以下各节中详细解释每个原因。

1.2.1 更好地利用单个 CPU

最常见的原因之一是能够更好地利用计算机中的资源。例如,如果一个线程正在等待通过网络请求的响应,则另一线程可以同时使用 CPU 来执行其他操作。此外,如果计算机具有多个 CPU,或者 CPU 具有多个执行核心,则多线程还可以帮助你的应用程序利用这些额外的 CPU 核心。

1.2.2 更好地利用多个 CPU 或 CPU 内核

如果计算机包含多个 CPU 或 CPU 包含多个执行核心,则你需要为应用程序使用多个线程才能使用所有 CPU 或 CPU 核心。单个线程最多只能使用一个 CPU,如上所述,有时甚至不能完全利用单个 CPU。

###1.2.3 通过响应性获得更好的用户体验

使用多线程的另一个原因是为了提供更好的用户体验。例如,如果你单击 GUI 中的按钮,并导致通过网络发送请求,那么哪个线程执行此请求就很重要。如果使用的线程也正在更新 GUI,则在 GUI 线程等待请求响应时,用户可能会遇到 GUI“挂起”的情况。取而代之的是,这样的请求可以由后台线程执行,因此 GUI 线程可以自由地同时响应其他用户请求。

1.2.4 通过公平地共享计算机资源获得更好的用户体验

第四个原因是在用户之间更公平地共享计算机资源。例如,假设一台服务器接收来自客户端的请求,并且只有一个线程来执行这些请求。如果客户端发送的请求要花费很长时间才能处理,则所有其他客户端的请求都必须等待,直到一个请求完成。通过让每个客户端的请求由其自己的线程执行,则没有一个任务可以完全垄断 CPU。

1.3 多线程与多任务

过去,一台计算机只有一个 CPU,并且一次只能执行一个程序。大多数小型计算机的功能实际上不足以同时执行多个程序,因此没有尝试过。公平地讲,许多大型机系统能够一次执行多个程序的时间比个人计算机长得多。

1.3.1 多任务

后来出现了多任务处理,这意味着计算机可以同时执行多个程序(AKA 任务或进程)。但是,这并不是真正的“同时”。单个 CPU 在程序之间共享。操作系统将在运行的程序之间进行切换,并在切换之前执行每个程序一会儿。

随着多任务处理,软件开发人员面临着新的挑战。程序不再假定拥有所有可用的 CPU 时间,也不拥有所有的内存或任何其他计算机资源。一个“好公民”程序应释放不再使用的所有资源,以便其他程序可以使用它们。

1.3.2 多线程

后来出现了多线程,这意味着你可以在同一程序中拥有多个执行线程。可以将执行线程视为执行程序的 CPU。当你有多个线程执行同一程序时,就像在同一程序中执行多个 CPU。

1.4 多线程很难

多线程是提高某些类型程序性能的好方法。但是,多线程处理比多任务处理更具挑战性。这些线程在同一程序中执行,因此同时在读取和写入相同的内存。这可能会导致在单线程程序中看不到的错误。在单个 CPU 机器上可能看不到其中一些错误,因为两个线程从未真正“同时”执行。但是,现代计算机配备了多核 CPU,甚至还配备了多个 CPU。这意味着可以由单独的内核或 CPU 同时执行单独的线程。

多CPU计算机上的多线程

如果一个线程在另一个线程写入内存时读取了一个内存,那么第一个线程最终将读取什么值?旧值?第二个线程写的值?还是两者之间混合的值?或者,如果两个线程正在同时写入同一内存位置,那么完成后将剩下什么值?由第一个线程写的值?第二个线程写的值?还是两个值的混合写入?

没有适当的预防措施,任何这些结果都是可能的。该行为甚至是不可预测的。结果可能会不时改变。因此,作为开发人员,重要的是要知道如何采取正确的预防措施-意味着要学习如何控制线程访问共享资源(如内存,文件,数据库等)。这是本 Java 并发教程所要解决的主题之一。

1.5 Java 中的多线程和并发

Java 是最早让开发人员可以使用多线程的语言之一。Java 从一开始就具有多线程功能。因此,Java 开发人员经常会遇到上述问题。这就是我在 Java 并发上编写此线索的原因。谨此提醒自己,以及提醒可能从中受益的其他 Java 开发人员。

本教程主要关注 Java 中的多线程,但是多线程中发生的某些问题类似于多任务和分布式系统中发生的问题。因此,对多任务和分布式系统的引用也可能出现在此线索中。所以叫法上称为“并发”,而不是“多线程”。

1.6 并发模型

第一个 Java 并发模型假定在同一应用程序中执行的多个线程也将共享对象。这种类型的并发模型通常称为“共享状态并发模型”。许多并发语言构造和实用程序都旨在支持此并发模型。

但是,自从编写第一本 Java 并发书籍以来,甚至自 Java 5 并发实用工具发布以来,并发体系结构和设计领域已经发生了很多事情。

共享状态并发模型导致许多并发问题,这些问题很难优雅地解决。因此,被称为“无共享”或“分离状态”的替代并发模型已经普及。在单独的状态并发模型中,线程不共享任何对象或数据。这避免了共享状态并发模型的许多并发访问问题。

出现了新的异步“独立状态”平台和工具包,例如 Netty,Vert.x 和 Play / Akka 和 Qbit。新的非阻塞并发算法已经发布,并且新的非阻塞工具(例如 LMax Disrupter)已添加到我们的工具箱中。Java 7 中的 Fork and Join 框架和 Java 8 中的 collection stream API 引入了新的函数式编程并行性。

随着所有这些新的发展,现在是时候更新本 Java 并发教程了。因此,本教程再次进行中。只要有时间编写新教程,教程就会被发布。

1.7 Java 并发学习指南

如果你不熟悉 Java 并发,建议你遵循以下学习计划。你也可以在此页面左侧的菜单中找到所有主题的链接。

通用并发和多线程理论:

Java 并发基础知识:

Java 并发的典型问题:

Java 并发构造可帮助解决上述问题:

进一步的主题:

2. 多线程的好处

多线程的最大好处是:

  • 更高的CPU利用率。
  • 在某些情况下,程序设计更简单。
  • 响应速度更快的程序。
  • 在不同任务之间更公平地分配CPU资源。

2.1 更好的CPU使用率

想象一个应用程序从本地文件系统读取和处理文件。假设从磁盘读取af文件需要5秒钟,而处理则需要2秒钟。然后处理两个文件

1
2
3
4
5
6
  5秒钟读取文件A
2秒处理文件A
5秒钟读取文件B
2秒处理文件B
-----------------------
总共14秒

从磁盘读取文件时,大部分的CPU时间都花在等待磁盘读取数据上。在这段时间内,CPU几乎处于空闲状态。它可能正在做其他事情。通过更改操作顺序,可以更好地利用CPU。查看以下顺序:

1
2
3
4
5
  5秒钟读取文件A
5秒读取文件B + 2秒处理文件A
2秒处理文件B
-----------------------
总共12秒

CPU等待读取第一个文件。然后,它开始读取第二个文件。当计算机的IO组件读取第二个文件时,CPU处理第一个文件。请记住,在等待从磁盘读取文件时,CPU大部分处于空闲状态。

通常,CPU在等待IO时可以做其他事情。不必是磁盘IO。它也可以是网络IO,也可以是来自计算机用户的输入。网络和磁盘IO通常比CPU和内存IO慢很多。

2.2 程序设计更简单

如果要在单线程应用程序中手动编写上述读取和处理的顺序,则必须跟踪每个文件的读取和处理状态。相反,您可以启动两个线程,每个线程仅读取和处理一个文件。这些等待线程将在等待磁盘读取其文件时被阻止。在等待时,其他线程可以使用CPU处理已读取的文件部分。结果是磁盘始终保持忙碌状态,将各种文件读入内存。这样可以更好地利用磁盘和CPU。编程也更容易,因为每个线程只需要跟踪一个文件即可。

2.3 更多响应程序

将单线程应用程序转换为多线程应用程序的另一个共同目标是实现响应速度更快的应用程序。想象一个服务器应用程序在某个端口上侦听传入的请求。收到请求后,它将处理该请求,然后返回监听。服务器循环如下所示:

1
2
3
4
while(服务器处于活动状态){
监听请求
流程请求
}

如果请求需要很长时间才能处理,则在这段时间内没有新客户端可以向服务器发送请求。只有在服务器正在侦听时,才能接收请求。

另一种设计是侦听线程将请求传递给工作线程,然后立即返回侦听。工作线程将处理该请求,并将回复发送给客户端。该设计概述如下:

1
2
3
4
while(服务器处于活动状态) { 
监听对
工作线程的请求请求
}

这样,服务器线程将尽快恢复监听。因此,更多的客户端可以将请求发送到服务器。服务器变得更加敏感。

桌面应用程序也是如此。如果单击启动长任务的按钮,并且执行任务的线程是更新窗口,按钮等的线程,则任务执行时应用程序将显示为无响应。而是可以将任务移交给工作线程。当工作线程忙于任务时,窗口线程可以自由响应其他用户请求。当工作线程完成时,它向窗口线程发出信号。然后,窗口线程可以使用任务结果更新应用程序窗口。具有工作线程设计的程序将对用户响应更快。

2.4 更公平地分配CPU资源

假设有一个服务器正在接收来自客户端的请求。然后想象一下,其中一个客户端发送了一个处理时间很长的请求,例如10秒。如果服务器使用单个线程处理了所有任务,则处理缓慢的请求之后的所有请求将被迫等待,直到处理完整个请求为止。

通过在多个线程之间划分CPU时间并在线程之间进行切换,CPU可以在多个请求之间更公平地共享其执行时间。这样,即使其中一个请求较慢,也可以与较慢的请求同时执行处理速度更快的其他请求。当然,这意味着执行慢速请求的速度甚至会更慢,因为它不会仅将CPU分配给处理它。但是,其他请求将不得不等待更短的时间来处理,因为它们不必等待缓慢的任务完成才可以处理它们。如果只有慢请求要处理,则仍可以将CPU单独分配给慢任务。

思考 11 不断update

在北京,会发现有很多店关闭了,也会发现有很多新开的店。而新开的店面很漂亮,那为什么那些旧的店不去重新装修和改进呢?

我用的电脑之前是MacBook Air,后来换成的MacBook Pro,发现开发效率更高了。

最近苹果发布了新的16英寸MacBook Pro,我会产生抵触心理,认为现在的就够用了,蝶式键盘也不错呀,很好了,我不需要更好的设备,开始不断的给自己找到理由来否定新的Mac电脑。

我认为每个人都会有这种心理,会给自己已经拥有的事物找理由,认为不需要改变,但这样会变得不客观和理性,会变得迟钝和墨守成规。我应该承认新款的Mac确实更加优秀,去想办法赚钱买它,而不是否定它。

去拥抱变化,而不能否定变化。

微软不也是从一开始的封闭变得越来越拥抱开源了吗,不拥抱改变的下场大概就是被抛弃把。

不改变就会凉,要不断更新自己。

  • Copyrights © 2015-2023 高行行
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信