xiaofang's blog

  • 首页

  • 标签

  • 分类

  • 归档

工厂模式

发表于 2019-11-05 | 更新于 2020-02-05 | 评论数: | 阅读次数:

工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

介绍

  • 意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
  • 主要解决:主要解决接口选择的问题。
  • 何时使用:我们明确地计划不同条件下创建不同实例时。
  • 如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
  • 关键代码:创建过程在其子类执行。
    应用实例: 1、您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。 2、Hibernate 换数据库只需换方言和驱动就可以。
  • 优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
  • 缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

例子

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
public interface Shape {

void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("构造圆形");
}
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("构造直角形");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("构造方形");
}
}
public class ShapeFactory {

public static Shape getShape(String shapeType) {

if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
}
if (shapeType.equalsIgnoreCase("RECTANGLE")) {
return new Rectangle();
}
if (shapeType.equalsIgnoreCase("SQUARE")) {
return new Square();
}
return null;
}
}
public class Main {
public static void main(String[] args) {
Shape circle = ShapeFactory.getShape("CIRCLE");
circle.draw();
Shape rectangle = ShapeFactory.getShape("RECTANGLE");
rectangle.draw();
Shape square = ShapeFactory.getShape("SQUARE");
square.draw();
}
}

核心思路

  • 需要若干实例
  • 构建工厂

策略模式

发表于 2019-11-05 | 更新于 2020-02-05 | 分类于 设计模式 | 评论数: | 阅读次数:

策略模式

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

介绍

  • 意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
  • 主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
  • 何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
  • 如何解决:将这些算法封装成一个一个的类,任意地替换。
  • 关键代码:实现同一个接口。
  • 应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3、JAVA AWT 中的 LayoutManager。
  • 优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
  • 缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
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
public interface GoToBeijing {

/**
* 去北京
*/
void toBeijing();

}
public class Bus implements GoToBeijing {

/**
* 坐汽车
*/
@Override
public void toBeijing() {
System.out.println("go to beijing byBus");
}
}
public class Fly implements GoToBeijing {

/**
* 坐飞机
*/
@Override
public void toBeijing() {
System.out.println("go to beijing take a plane");
}
}
public class StrategyContext {

private GoToBeijing toBeijing;

public StrategyContext(GoToBeijing toBeijing) {
this.toBeijing = toBeijing;
}

public void toBeijing() {
toBeijing.toBeijing();
}
}

public class Main {

public static void main(String[] args) {

StrategyContext context = new StrategyContext(new Bus());
context.toBeijing();
StrategyContext context2 = new StrategyContext(new Fly());
context2.toBeijing();
}

}

核心思路

  • 事件的抽象
  • 实现方法分类
  • 调度实现

与命令模式很相像,我觉得他们之间的区别在于解决问题的角度不一样,命令模式侧重于请求和执行2者解耦,策略强调的是事务多种解决方案或者途径。

Maven 插件讲解

发表于 2019-07-07 | 分类于 工具 | 评论数: | 阅读次数:

一、maven 可用插件信息

  • Apache(官方,稳定)
    • 详细列表:地址
    • 下载列表:地址

二、maven3个内置生命周期

每个生命周期相互独立,且包含若干阶段。

  • clean
  • default(build)
  • site

三、3个生命周期包含的阶段

每个阶段相当于一个接口,具体的实现由插件来实现。

1、clean

  • pre-clean执行一些清理前需要完成的工作。
  • clean清理上一次构建生成的文件。
  • post-clean执行一些清理后需要完成的工作。

2、 default

default 生命周期定义了真正构建时所需要执行的所有步骤,它是所有生命周期中最核心的部分。

  • validate
  • initialize
  • generate-sources
  • process-sources 处理项目主资源文件。一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中。
  • generate-resources
  • process-resources
  • compile 编译项目的主源码。一般来说,是编译src/main/java目录下的Java文件至项目输出的主classpath目录中。
  • process-classes
  • generate-test-sources
  • process-test-sources处理项目测试资源文件。一般来说,是对src/test/resources目录的内容进行变量替换等工作后,复制到项目输出的测试classpath目录中。
  • generate-test-resources
  • process-test-resources
  • test-compile编译项目的测试代码。一般来说,是编译src/test/java目录下的Java文件至项目输出的测试classpath目录中。
  • process-test-classes
  • test 使用单元测试框架运行测试,测试代码不会被打包或部署。
  • prepare-package
  • package接受编译好的代码,打包成可发布的格式,如JAR。
  • pre-integration-test
  • integration-test
  • post-integration-test
  • verify
  • install 将包安装到Maven本地仓库,供本地其他Maven项目使用。
  • deploy 将最终的包复制到远程仓库,供其他开发人员和Maven项目使用。

site

site生命周期目的是建立和发布站点,maven能够基于pom配置自动生成一个简单站点,主要包括以下阶段:

  • pre-size 执行在站点生成之前的准备工作;
  • site 生成项目站点文档;
  • post-site 执行一些在生成项目站点之后需要完成的工作;
  • site-deploy 将生成的项目站点发布到服务器上

总结:生命周期:阶段=1:n,阶段:插件=1:1,插件:插件目标=1:n,阶段:插件目标=1:n ,也就是多生命周期,阶段都是抽象,具体的体现在插件的目标

四、maven 插件与绑定阶段

maven 为了更好的体验,默认内置几个插件,让用户在无感情况下完成项目的构建。

1、maven内置插件与绑定阶段

插件 功能 绑定阶段
maven-clean-plugin 清理上一次执行创建的目标文件 clean
maven-resources-plugin 处理源资源文件和测试资源文件 resources,testResources
maven-compiler-plugin 编译源文件和测试源文件 compile,testCompile
maven-surefire-plugin 执行测试文件 test
maven-jar-plugin 创建jar jar
maven-install-plugin 安装 jar,默认将创建生成的 jar 拷贝到 .m2/repository 下面 install
maven-deploy-plugin 发布jar 到maven仓库,例如公司的私服 deploy

2、maven插件目标调用方式

插件是实现,目标是插件的具体体现,所以目标是我们关心的核心点。

  • 默认绑定
    maven 默认将生命周期的若干阶段绑定了某些插件的目标,注意某些。即,阶段:目标=1:n,这里的n是可以包含0或大于1。
    例如:mvn clean (默认将clean这个生命周期的clean阶段绑定到了maven-clean-plugin插件的clean目标),这里需要说明下
    maven-clean-plugin 只有一个目标,其他插件可能拥有n个目标,具体需要到maven官网 上查看。相同的,mvn package,mvn install 也是一样。
  • 指定调用
    我们知道maven官方的插件,一般是这样的模式,artifactId 为 maven-archetype-plugin ,我们可以使用 mvn archetype:goal-name 命令的形式来调用具体的插件目标。

说明:mvn xx:goal-name 中的xx以为是archetype按道理是不太准确,它应该是插件的唯一描述groupId:artifactId(前缀),但是在maven的官方上一般以archetype为插件的前缀。 我们也可以在引用插件的地方,用configuration 配置它的前缀:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<project>
...
<build>
...
<plugins>
...
<plugin>
<artifactId>maven-compile-plugin</artifactId>
<version>2.3</version>
<configuration>
...
<goalPrefix>customPrefix</goalPrefix>
</configuration>
</plugin>
</plugins>
</build>
</project>

这样我们可以使用 mvn customPrefix:goal 来调用compile插件的某个目标了。

五、推荐几个有用小插件

1、maven–dependency-plugin

这个插件有很多的目标,详情可见官方定义,我常用的:

  • dependency:analyze 分析这个项目的依赖关系,并确定哪些是声明了且使用了,声明没使用等(不是很准确,特别在配置文件里面使用了依赖时候)作为大致分析。
  • dependency:copy 将依赖和source 分离,例如:springBoot 中Fatjar问题解决.
  • dependency :tree 显示该项目的依赖关系树。

2、maven-assembly-plugin

这个插件在项目制作分发包时候使用,需要定义assembly.xml的元数据文件来表述打包。你可以这么理解,当需要一个压缩文件,包含项目的jar包,源代码,执行脚本等其他,就可以用它来指定这个文件,支持zip,tar.gz jar war 等格式。

3、maven-help-plugin

它是个maven提供的一个很有用的工具,可以帮助我们分析了解当前生效的profiles,pom,settings 文件和查询某个插件信息。例如

  • 当前生效pom
    mvn help:effective-pom
  • 当前生效的settings
    mvn help:effective-settings
  • 查询插件详情(用法和包含几个目标)
    mvn help:describe -Dplugin=插件的前缀 -Ddetail

Java 里面的SPI

发表于 2019-06-23 | 分类于 基础 | 评论数: | 阅读次数:

今天在看web.xml为什么逐渐淡出我们的视野,重温了下JDK的SPI机制(Service Provider Interface)。

SPI 是什么?

面向接口编程+配置文件!
对,我总结的就是一句话。看看我们日常使用不同数据库驱动,不同日志框架,是不是都在使用SPI机制呢?下面我用数据库驱动来举例。

JDBC驱动使用SPI

一、概述

我们都知道数据库驱动 java.sql.Driver,针对不同数据库厂商有不同的实现,翻翻我们mysql驱动jar包,依次找到META-INF/services/java.sql.Driver,看看里面有啥?

1
2
3
# mysql 驱动实现类
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

我们看看Driver类的static方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
// 注册
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
Class.forName().newInstance()
}
}

二、加载驱动,获取连接

注册Driver到DriverManager -> DriverManager.getConnection(…) 获取连接

新(SPI机制)、老(反射)2种方式

1、新方式,SPI机制,省略Class.forName(“xxx.xxx.xxx”)方式(jar包下所需文件如概述描述)

驱动从 5.x 开始jar包下必须要有 META-INF/services/java.sql.Driver。

1
2
3
// 会调用DriverManager的static方法,里面会寻找驱动jar包下面META-INF/services/java.sql.Driver 文件
// 初始化Driver,当然也就调用了Driver的static方法
Connection connection = DriverManager.getConnection(URL, username, pwd);
2、老方式,通过Class.forName(“xxx.xxx.xxx”)反射

驱动4.x一般这样来写

1
2
3
4
// 反射,会调用Driver里static方法,注册Driver到DriverManager
Class.forName("com.mysql.jdbc.Driver");
// 从DriverManager 里面获取连接
Connection connection = DriverManager.getConnection(URL, username, pwd);

2种方式讲解

  • 新的
1
2
3
4
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

ServiceLoader实现spi机制

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
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//查找mysql驱动jar下META-INF/services/java.sql.Driver
//初始化它,调用 static方法,注册驱动(同下分析)
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}

后续流程同下面老的讲解

  • 老的
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//Driver里static方法
static {
try {
//向DriverManager 注册Driver
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
// registerDriver 方法
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {

registerDriver(driver, null);
}

// 最终方法,注册Driver到DriverManager
public static synchronized void registerDriver(java.sql.Driver driver,DriverAction da)
throws SQLException {
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
// 获取连接
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
//最终调用
return (getConnection(url, info, Reflection.getCallerClass()));
}

private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
// 从registeredDrivers获取连接信息
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
// 获取连接
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}

我们可以看到SPI机制核心在于:ServiceLoader、META-INF/services/需要实现接口全定限名 文件。ServiceLoader位于java.util包下,其load方法就是实现SPI的关键。有兴趣可以自己实现个SPI,试验下!

StopWatch

发表于 2019-06-22 | 分类于 小工具 | 评论数: | 阅读次数:

在日常的开发中我们需要统计一端代码的执行时间,我们可以这么做:

public static void main(String[] args) throws Exception {
       Long start = System.currentTimeMillis();
       Thread.sleep(1000);
       Long stop = System.currentTimeMillis();
       System.out.println("执行耗时:" + (stop - start));
   }

我在看springBoot源码的时候,看到了StopWatch,觉得好奇,跟进去看看源码,发现它也可以统计代码的执行耗时时间,看起来更加高大上,也更方便,我们看看它怎么使用:

public static void main(String[] args) throws Exception {
    StopWatch sw = new StopWatch();
    sw.start("t1");
    Thread.sleep(1000);
    sw.stop();
    sw.start("t2");
    Thread.sleep(2000);
    sw.stop();
    // 表格化输出
    System.out.println(sw.prettyPrint());
    System.out.println("***************************");
    //总结性输出
    System.out.println(sw.shortSummary());
    System.out.println("***************************");
    // 只输出耗时
    System.out.println(sw.getTotalTimeMillis());
}

springBoot 打包 fat、thin jar

发表于 2019-06-16 | 评论数: | 阅读次数:

一、什么是fat、thin jar

springBoot项目可以运行java -jar xxx.jar 来启动,默认情况下它会把所有的依赖都打到一个jar里面,称作fat jar。但是一般情况下很多外部依赖的jar大多数不可变,若是每次的jar都是很大,势必在网络不好的情况下上传到服务器很慢。基于此,我们可以利用maven的2个插件也可以打出thin jar。

备注:java -jar xx.jar 是java自带的命令,和sprignBoot没半毛钱关系,因此我们只需要按照这个jar文件的规范,也可以将任何项目打包成可执行的jar。

二、MANIFEST.MF 文件

每一个jar包下面都谁有它,可见其重要性,它列出了jar包一些信息/清单。

  • 正常的springBoot可执行jar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Manifest-Version: 1.0
Implementation-Title: af-app
Implementation-Version: 0.0.1-SNAPSHOT
// springBoot启动类
Start-Class: com.af.app.afapp.AfAppApplication
//类文件
Spring-Boot-Classes: BOOT-INF/classes/
//依赖路径
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.1.5.RELEASE
Created-By: Maven Archiver 3.4.0
//通过它来启动 Start-Class
Main-Class: org.springframework.boot.loader.JarLauncher
  • mvn-jar-plugin 插件打出来的可执行jar
1
2
3
4
5
6
7
8
9
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: yons
// 类path,依赖的所有jar
Class-Path: ../lib/data-asset-tag-api-1.0.0.jar ...
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_201
// 启动类
Main-Class: com.dtwave.asset.tag.provider.DataAssetTagStarter

三、springBoot 可执行jar的maven插件

1
2
3
4
5
6
7
8
9
10
11
12
 <plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- 可选-->
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>

这里我们直接给出springboot提供好的插件,就可以打出可执行的jar。注意若我们的工程没有继承spring-boot-starter-parent(这里面有了repackage 这个阶段),则还需要在上述的插件里面加个repackage,它就是将jar包可执化的关键。

四、maven-jar-plugin+maven-dependency-plugin 插件

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
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<!--设置manifest.mf文件-->
<manifest>
<!-- 添加依赖jar路径 Class-Path -->
<addClasspath>true</addClasspath>
<!-- 设置入口程序 -->
<mainClass>com.af.app.afapp.AfAppApplication</mainClass>
<classpathPrefix>../libs</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<!--拷贝项目依赖-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!--自动在target目录下新建libs目录放依赖-->
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>

maven-jar-plugin 插件可以指定主程序入口,和可搜索的类路径。
dependency-plugin 主要用于依赖的copy,瘦身jar。

有兴趣的朋友可以研究下springBoot 启动的原理,它是怎么使用org.springframework.boot.loader.JarLauncher 来找到我们的启动类,完成程序的启动的。

读写锁

发表于 2019-06-06 | 更新于 2020-02-05 | 分类于 并发 | 评论数: | 阅读次数:

读写锁

ReadWriteLock 读写锁,提供了很好的读写分离,读读之间时并行,读写/写写之间串行,先看个例子

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
public class ReadWriteLockDemo {
final static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
final static Lock readLock = readWriteLock.readLock();
final static Lock writeLock = readWriteLock.writeLock();

public void read(Lock lock) throws Exception {
Thread read = new Thread(() -> {
lock.lock();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
read.start();
}

public void write(Lock lock) throws Exception {
Thread write = new Thread(() -> {
lock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
write.start();
}

public static void main(String[] args) throws Exception {
ReadWriteLockDemo demo = new ReadWriteLockDemo();
for (int i = 0; i < 10; i++) {
demo.read(readLock);
demo.write(writeLock);

}
}
}

ReadWriteLock 与ReentrantLock 之间作用不同,ReentrantLock任何读写之间都是串行,在读多写少的场景读写锁可以发挥很大的作用。

CountDownLatch

发表于 2019-06-05 | 更新于 2020-02-05 | 分类于 并发 | 评论数: | 阅读次数:

倒计时计数器

CountDownLatch 为一个倒计时计数器,可以将线程固定在某几个程序运行结束后再运行,比如我们航天工程里面火箭发射,前置很多检查需要都都准备就绪才能发射。

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
public class CountDownLatchDemo {
static CountDownLatch countDownLatch = new CountDownLatch(5);
static ReentrantLock lock = new ReentrantLock();
static AtomicInteger count = new AtomicInteger(1);

public static void main(String[] args) throws Exception {
ExecutorService service = Executors.newCachedThreadPool();
Thread t = new Thread(() -> {
try {
lock.lock();
Thread.sleep(100);
System.out.println("火箭发射前设备状态检查" + count.getAndIncrement());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
countDownLatch.countDown();
});
for (int i = 0; i < 5; i++) {
service.execute(t);
}
countDownLatch.await();
System.out.println("设备状态检查执行完了,开始发射!");
}
}
  • 打印结果
    • 火箭发射前设备状态检查1
    • 火箭发射前设备状态检查2
    • 火箭发射前设备状态检查3
    • 火箭发射前设备状态检查4
    • 火箭发射前设备状态检查5
    • 设备状态检查执行完了,开始发射!

可重入锁

发表于 2019-06-05 | 更新于 2020-02-05 | 分类于 并发 | 评论数: | 阅读次数:

可重入锁

  • 一、可重入锁 ReentrantLock

    线程基础里面我介绍了线程保持同步,使用的是synchronized 关键字配合wait和notify来使用,JDK5 以后提供的并发包里ReentrantLock 完全可以替代他们。

  • 演示个累加例子

    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    	public class ReentryLock {
    static int sum = 0;

    public static void main(String[] args) {
    ReentrantLock reentrantLock = new ReentrantLock();
    Runnable r = () -> {
    for (int i = 0; i < 100; i++) {
    reentrantLock.lock();
    sum++;
    reentrantLock.unlock();
    }
    };
    Thread t1 = new Thread(r);
    Thread t2 = new Thread(r);
    t1.start();
    t2.start();
    try {
    t1.join();
    t2.join();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(sum);
    }
    }
    ```
    * 运行结果:200
    * 关键方法/功能
    * 可以中断响应
    * 锁申请时等待限时
    * 公平锁
    * 类比
    - **可以中断:**synchronized在等待锁时候,要么等待,要么获得锁,而ReentryLock可以优雅的中断等待。小明周末打电话给好朋友小刚取打篮球,小刚说自己有点事需要等下,好了给小明电话,于是小明等待小刚的电话,等待过程中小明突然接到女朋友电话,她买好了电影票,电影还有半个小时开场了,这下肯定去陪女朋友,不然单身一辈子了,于是立马电话和小刚说自己有急事不去打篮球了。相反小刚手头事一时半会忙不完,也可以电话给小明,今天不能去打篮球了。这就是中断的好处。下面看看这个简答的例子:

    ```java
    public class ReentryLockInter {
    static ReentrantLock lock = new ReentrantLock();//小明
    static ReentrantLock lock2 = new ReentrantLock();//小刚
    static boolean isInterrupt = false;
    static String friend ;

    private static void setInterrupt(boolean isInterrupt,String friend) {
    ReentryLockInter.isInterrupt = isInterrupt;
    ReentryLockInter.friend = friend;
    }

    public static void main(String[] args) {
    Runnable r = () -> {
    if (isInterrupt) {
    try {
    lock.lockInterruptibly();
    System.out.println(Thread.currentThread().getName() + "突然有急事,如是打电话给"+friend+",今天有事,不能去打篮球了");
    lock2.lockInterruptibly();
    } catch (InterruptedException e) {
    e.printStackTrace();
    } finally {
    if(lock.isHeldByCurrentThread())lock.unlock();
    if(lock2.isHeldByCurrentThread())lock2.unlock();
    }
    }
    };
    Thread t1 = new Thread(r);
    t1.setName("小明");
    setInterrupt(true,"小刚");
    Thread t2 = new Thread(r);
    t2.setName("小刚");
    setInterrupt(true,"小明");
    t1.start();
    t2.start();
    }
    }
    • 打印结果
      小刚突然有急事,如是打电话给小明,今天有事,不能去打篮球了
      小明突然有急事,如是打电话给小明,今天有事,不能去打篮球了
  • 锁申请时等待限时:这个功能可以很好的·防止死锁的产生。还是拿小明周末约小刚打篮球的事情,这次小明女朋友想要他下午5点去陪她看电影,而目前是早上8点,还有好几个小时,于是小明可以等小刚一段时间忙好,相约去打篮球。

    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
    63
    64
    65
    66
    67
    68
    69
    public class ReentryLockTest3 {
    static ReentrantLock lock = new ReentrantLock();//小明
    static ReentrantLock lock2 = new ReentrantLock();//小刚
    static int time = 0;
    static String friend;

    private static void setInterrupt(int time, String friend) {
    ReentryLockTest3.time = time;
    ReentryLockTest3.friend = friend;
    }

    public static void main(String[] args) {
    Runnable r = () -> {
    try {
    if (lock.tryLock(time, TimeUnit.HOURS)) {
    System.out.println(Thread.currentThread().getName() + "等" + friend + "一起去打篮球");
    } else {
    lock.lockInterruptibly();
    lock2.lockInterruptibly();
    System.out.println(Thread.currentThread().getName() + "打电话给" + friend + "说,等你太久了,今天不去打篮球了");
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    } finally {
    if (lock.isHeldByCurrentThread()) lock.unlock();
    if (lock2.isHeldByCurrentThread()) lock2.unlock();
    }
    };
    Thread t1 = new Thread(r, "小明");
    setInterrupt(5, "小刚");
    t1.start();
    Thread t2 = new Thread(r, "小明");
    setInterrupt(0, "小刚");
    t2.start();
    }
    }
    ```
    * 打印结果
    小明等小刚一起去打篮球
    小明打电话给小刚说,等你太久了,今天不去打篮球了

    - **公平锁:**小明因为女朋友,取消了和小刚约一起去打篮球,来到电影院,今天电影院人真多,原来是一个当红电影明星来宣传电影,需要排队进场,如是他和女朋友去一起排队,大家都是先来先到顺序进场。想想如果此时有个人搞特权,在队伍的最后面被安排先进场了,大家肯定不爽。这就是公平和非公平锁。公平锁实现成本较高,因为它的实现是维护一个有序队列

    ```java
    public class ReentryLockTest4 {
    static ReentrantLock fairLock = new ReentrantLock(true);//默认不写为false

    static Runnable enteringCinema = () -> {
    try {
    fairLock.lock();
    System.out.println(Thread.currentThread().getName() + "进场");
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    if (fairLock.isHeldByCurrentThread()) fairLock.unlock();
    }
    };

    public static void main(String[] args) {
    Thread t1 = new Thread(enteringCinema, "路人甲");
    Thread t2 = new Thread(enteringCinema, "小明");
    Thread t3 = new Thread(enteringCinema, "小明女朋友");
    Thread t4 = new Thread(enteringCinema, "路人乙");
    t1.start();
    t2.start();
    t3.start();
    t4.start();
    }
    }
  • 打印结果
    路人甲进场
    小明进场
    小明女朋友进场
    路人乙进场

  • Condition,ReentrantLock好帮手

public class ConditionDemo {
    static ReentrantLock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();


    static Runnable r = () -> {
        lock.lock();
        try {
            System.out.println("执行condition.await()方法,暂停方法");
            condition.await();
            System.out.println("恢复执行");//end
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    };

    public static void main(String[] args) {
        Thread t1 = new Thread(r);
        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock.lock();
        condition.signal();
        lock.unlock();//若这个被注释,则end执行不到,得到的和suspend效果一样,线程被长久挂起,很危险
        System.out.println("执行condition.signal()方法");

    }
}

循环栅栏

发表于 2019-06-05 | 更新于 2020-02-05 | 分类于 并发 | 评论数: | 阅读次数:

循环栅栏

CyclicBarrier 和CountDownLatch整体的作用差不多,都是协调线程在某个点触发以后的任务,但是CountDownLatch 没有循环的功能。比如:部队里面士兵需要区演练,首先是集合再做演练任务。看看下面的例子

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
63
64
65
66
public class CyclicBarrierDemo {
public static class Soldier implements Runnable {
private String soldier;
private final CyclicBarrier cyclic;

public Soldier(String soldier, CyclicBarrier cyclic) {
this.soldier = soldier;
this.cyclic = cyclic;
}

@Override
public void run() {
try {
cyclic.await();
doWork();
cyclic.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
void doWork() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(soldier + "任务完成");
}
}


public static class BarrierRun implements Runnable {
boolean flag;
int N;

public BarrierRun(boolean flag, int n) {
this.flag = flag;
N = n;
}
@Override
public void run() {
if (flag) {
System.out.println("司令:[士兵)" + N + "个,任务完成!]");
} else {
System.out.println("司令:[士兵)" + N + "个,集合完毕!]");
flag = true;
}
}
}


public static void main(String[] args) {
final int N = 10;
Thread[] allSoldier = new Thread[N];
boolean flag = false;
CyclicBarrier cyclicBarrier = new CyclicBarrier(N, new BarrierRun(flag, N));
System.out.println("集合队伍!");
for (int i = 0; i < N; i++) {
System.out.println("士兵" + i + "报道!");
allSoldier[i] = new Thread(new Soldier("士兵" + i, cyclicBarrier));
allSoldier[i].start();
}
}
}
  • 打印结果
    集合队伍!
    士兵0报道!
    士兵1报道!
    士兵2报道!
    士兵3报道!
    士兵4报道!
    士兵5报道!
    士兵6报道!
    士兵7报道!
    士兵8报道!
    士兵9报道!
    司令:[士兵)10个,集合完毕!]
    士兵9任务完成
    士兵7任务完成
    士兵6任务完成
    士兵0任务完成
    士兵8任务完成
    士兵2任务完成
    士兵4任务完成
    士兵3任务完成
    士兵1任务完成
    士兵5任务完成
    司令:[士兵)10个,任务完成!]
1234

安迪·梵

keep it simple and stupid
34 日志
13 分类
4 标签
RSS
E-Mail GitHub Weibo
© 2020 安迪·梵
本站总访问量 次 | 有人看过我的博客