java文章迁移

This commit is contained in:
结发受长生 2018-05-09 20:54:50 +08:00
parent 677714d4c7
commit 7e0b74a322
21 changed files with 852 additions and 0 deletions

View File

@ -0,0 +1,99 @@
---
title: Lamdba表达式(1)
date: 2018-5-9 20:45:52
categories:
- Java
---
`Lamdba表达式`是Java8的一项重要的新特性
它是基于匿名内部类演化出的一种更加抽象的语法形式
由编译器去推断并包装为常规的代码
<!-- more -->
官方的解释为
> 一个不用被绑定到一个标识符上,并且可能被调用的函数
可以理解为是 一段带有输入参数的可执行语句块
```java
List<String> names = new ArrayList<String>();
//在List当中加入若干元素
Collections.sort(names, (o1,o2)->o1.compareTo(o2));
```
上述代码中用到的Lamdba表达式其实就是相当于构建了一个实现了Comparator接口的匿名内部类
等价于以下代码
```java
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
```
也许下面的代码能更好地体现Lamdba表达式就是匿名类的另外一种写法
```java
public class Main {
public static void main(String[] args) {
Demo d = ()->{return "aa";};
d.func();
}
}
interface Demo {
public String func();
}
```
---
#### Lamdba表达式语法
```java
(Type1 param1, Type2 params,...,TypeN paramN) -> {
//若干语句...
return 表达式;
}
```
这是一般语法 , 也就是最完整的一种语法
但是通常用到Lamdba表达式的时候 , 都是方法中的语句并不复杂 , 逻辑简单的情况
否则会造成代码可读性很差
所以Lamdba就有很多种简化版 , 实际用的也多是简化版
1. 编译器可以根据上下文推断参数的类型 , 大多数情况下参数类型可以省略
```java
(param1, params,...,paramN) -> {
//若干语句...
return 表达式;
}
```
2. 当参数只有一个 , 可以省略小括号
```java
param -> {
//若干语句...
return 表达式;
}
```
3. 当方法中只包含一条语句 , 可以省略大括号 return 和 最后的分号
```java
param -> 表达式
```
---
其他要点
+ 与匿名类中的方法一样 , Lamdba可以访问其所在的外部类的成员 , 也可以访问其所在方法中的局部变量
+ Lamdba表达式中的this指向的并不是这个匿名类对象 , 而是指向其外部类对象
所以如果是在一个静态方法中 , 则不能使用this
---
#### 方法引用
`方法引用`可以在某些条件成立的情况下 , 更加简化Lamdba表达式的声明 , 格式有以下3种
+ **对象名称 :: 非静态方法**
+ **类名称 :: 静态方法**
+ **类名称 :: 非静态方法**
前两种方式类似
例如`System.out::println`
等价于`x->System.out.println(x);`
最后一种方式 , 相当于把Lamdba表达式的第一个参数当做该方法的目标对象
例如`String::toLowerCase`
等价于`x->x.toLowerCase();`
#### 构造器引用
语法是
+ **类名 :: new**
例如 `BigDecimal::new`
等价于`x->new BigDecimal(x)`

View File

@ -0,0 +1,73 @@
---
title: Lamdba表达式(2)-Stream
date: 2018-5-9 20:46:13
categories:
- Java
---
有了Lamdba表达式 , Java就具有了进行函数式编程的条件
但是作为彻底的面向对象语言 , 并不支持函数的独立存在
所以JDK1.8添加了`Stream`以及一些相关的接口
<!-- more -->
Stream的特点可以概括如下
1. Stream是元素的序列 , 看起来有点类似Iterator
2. 可以支持顺序和并行聚合的操作
可以把Stream看成一个高级版本的Iterator
Iterator只能逐个对元素进行遍历 , 然后执行某些操作
对于Stream , 只需要给出对元素进行处理的回调函数
就可以对这个元素序列进行一些操作
> 可以参考underscore对数组或者对象进行处理的一些方法
> 编程方式上十分类似
```java
List<Integer> nums = Lists.newArrayList(5,2,1,0,null,56);
Stream<Integer> stream = nums.stream();
//过滤掉序列中的null值 并获得剩余元素的数量
long count = stream.filter(num -> num!=null).count();
System.out.println(count);//5
```
Lists是`Guava`当中的一个工具类 , 这里用它来产生一个List对象
> Stream当中的很多**非聚合**方法 , 都是返回Stream对象 , 可以进行连缀
> 但是**注意** : 每次调用后的结果 , 都是产生一个新的Stream对象 , 而不再是原Stream对象
> 此时如果再对原对象进行任何操作 , 都会抛出异常
#### 创建Stream
有两种方式可以创建一个Stream对象
1. 通过Stream接口当中的静态工厂方法
2. 通过Collection接口的默认方法`stream()` , 上面的例子用的就是这种方式
**Stream接口中的静态工厂方法**
+ `of方法` - 有两个重载形式 , 一个是接收单一值 , 一个是接收多个值
```java
Stream<Integer> intStream = Stream.of(10,20,30);
Stream<String> strStream = Stream.of("test");
```
+ `generate方法` - 获得一个生成器
这个方法产生的Stream是一个**惰性序列** , 也就是它并不保存这个序列中所有的内容 , 每次需要获取的时候 , 再去生成
通过这种方式 , 可以节约大量的内存 , 也可以获得一个无限长的序列
```java
Stream<Integer> randomNums = Stream.generate(()->(int)(Math.random()*1000));
randomNums.limit(20).forEach(System.out::println);
```
#### 常用的非聚合操作
+ `distinct` - 去除序列中的重复元素 ( 依赖元素的equals方法 )
+ `filter` - 对于实参函数执行结果为false的 , 从序列中去除
+ `map` - 对于元素逐个使用实参函数进行处理 , 并将返回值组装成一个新的序列
+ `peek` - 克隆原Stream产生一个新的Stream , 并提供一个消费函数 , 当新Stream中的每个元素被消费的时候都会调用该函数
+ `limit` - 截取前若干个元素
+ `skip` - 丢弃前若干个元素
+ `sorted` - 对序列中的元素进行排序 ( 可以指定Comparator )
#### 常用的聚合操作
**聚合操作**(也称为折叠)接受一个元素序列为输入,反复使用某个合并操作,把序列中的元素合并成一个汇总的结果。
+ `collect` - 将所有的元素放入到一个Collection容器当中 ( 属于可变聚合 )
+ `reduce` - 将前两个元素使用指定函数进行聚合 , 将结果与第三个元素进行聚合 , 依次进行下去
+ `count` - 获得序列中元素的数量
----
Stream当中的方法大部分都可以根据名称猜出意思
就不一一介绍了 , 详细内容参阅JDK源码

View File

@ -0,0 +1,94 @@
---
title: Maven(1)-初见
date: 2018-5-9 20:18:14
tags:
- maven
categories:
- Java
---
我们在工作中可能会在IDE当中有很多项目
这些项目多数需要引用一些第三方的jar包
但是对于相同类型的项目 , 引用的jar包很可能是重复的
在以往 , 我们需要在每个项目中都拷贝一份这些jar包 , 以保证这些项目可以独立运行 , 这样显然不好
而且对于同一个jar包来说 , 它也会存在不同的版本 , 缺乏统一的管理
<!-- more -->
对于较为复杂的项目 , 可能会有主项目和若干个子项目 , 他们之间的依赖关系也难以维护
`Maven`就是用来解决项目管理中遇到的这些问题
它引入了`仓库`的概念 , 实现了对jar包的统一管理
#### 安装与配置
1. 解压存放到一个固定的目录当中
然后配置环境变量 , 例如
`MAVEN_HOME=D:/maven-3.3.9`
在PATH当中添加
`%MAVEN_HOME%/bin`
> Mac平台直接修改 .bash_profile文件
在控制台输入`mvn -version`正确显示maven的版本代表配置正确
2. 修改maven目录下conf/setting.xml文件
添加本地仓库位置 , 例如
```xml
<localRepository>/Users/Sookie/Documents/apache-maven-3.3.9/mavenLib</localRepository>
```
3. 在myeclipse/eclipse当中配置maven
![Alt text](/images/Java/maven1.png)
点击Add选择到Maven的根目录即可
![Alt text](/images/Java/maven2.png)
然后在User Setting里面设置setting.xml文件的根目录( 就是上一步当中修改的setting.xml文件 )
#### 第一个Maven项目
在myeclipse当中新建一个maven项目
结构如下
![Alt text](/images/Java/maven3.png)
`src/main/java`用于存放源代码
`src/main/test`用于存放测试代码
`target`目录用于存放编译 打包后的输出文件
这是maven项目的通用约定
如果要引入第三方jar包 , 需要编辑pom.xml文件
> 这里在引入jar包的时候 , 如果本地仓库中没有 , 则会去maven官方的服务器上去下载
> 下载后保存到本地仓库
> 如果官方没有这个jar包 , 也可以自己放进仓库里
这里尝试引入几个jar包
```xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>maven_demo</groupId>
<artifactId>hello_maven</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.2</version>
</dependency>
<dependency>
<groupId>mongo</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>mongo</groupId>
<artifactId>morphia</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
</project>
```
观察本地仓库的目录结构就可以发现 , 从左到右依次是groupId artifactId version
![Alt text](/images/Java/maven4.png)
如果要放入自己的jar包 , 按照这个结构去创建目录即可
引入以后 , 刷新项目 , 引入的jar包就会出现在这里
![Alt text](/images/Java/maven5.png)
在代码中可以直接使用

View File

@ -0,0 +1,34 @@
---
title: Maven(2)-搭建web项目
date: 2018-5-9 20:25:23
tags:
- maven
categories:
- Java
---
在Maven当中创建一个web项目的步骤如下
<!-- more -->
##### (1) 新建maven项目
打包方式选择`war`
![Alt text](/images/Java/maven_web1.png)
##### (2) 设置项目属性
![Alt text](/images/Java/maven_web2.png)
点击上图中箭头所指链接
![Alt text](/images/Java/maven_web3.png)
将webapp作为web项目的根目录
![Alt text](/images/Java/maven_web4.png)
> 此时webapp下面就已经生成了web项目的基本结构
![Alt text](/images/Java/maven_web5.png)
##### (3) 修改发布规则
> 由于web项目需要发布到tomcat运行
所以需要指定发布的规则 , 也就是把哪个目录下的文件发布到tomcat当中
项目属性 -> Deployment Assembly
![Alt text](/images/Java/maven_web6.png)
表示将webapp目录下的文件发布到tomcat中对应项目文件夹的根目录下
将maven引入的jar包发布到WEB-INF/lib目录下
> 这里可以将两个 test 源码文件夹去掉 , 单元测试代码可以不需要发布

View File

@ -0,0 +1,127 @@
---
title: Maven(3)-从入门到重新入门
date: 2018-5-9 20:29:31
tags:
- maven
categories:
- Java
---
`Maven`是基于项目对象模型 ( POM ) , 可以通过描述信息来管理项目的构建 报告 和文档的软件项目管理工具
简而言之 , 使用maven可以帮助我们更高效地管理项目
它也是一套强大的构建工具 , 覆盖了编译 测试 运行 清理 打包部署各项构建周期
<!-- more -->
#### 修改maven的配置
在maven根目录/conf/setting.xml当中 , 可以修改maven的配置
可以修改本地仓库所在位置 , 比如
```xml
<localRepository>
/Users/Sookie/Documents/maven_lib
</localRepository>
```
由于官方的远程仓库位于国外 , 在国内的访问速度比较捉急
所以也可以配置一个国内的镜像地址
比如在`<mirrors>`节点当中添加阿里云的maven镜像
```xml
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
```
#### 创建符合maven项目目录结构的项目
这里需要用到`archetype`这个插件
执行
```bash
mvn archetype:generate
```
来构建一个新的maven项目
执行过程中需要指定archetype的版本 , 使用默认即可
然后输入该项目的groupId , artifactId 等内容
#### maven的常用命令
```bash
#删除项目中的target目录
mvn clean
#项目编译
mvn compile
#运行所有测试用例
mvn test
#打包项目
mvn package
#安装jar包到本地仓库
mvn install
```
安装到本地仓库就意味着其他的项目可以引入这个jar包
#### maven的生命周期
上面提到的几个常用命令 , 其实就是maven的几个生命周期
clean compile test package install
后面的4个统称为**default** , 也叫项目构建的阶段
之所以称之为生命周期 , 是因为在执行后面的操作的时候 , 也会自动执行前面的操作
比如执行`mvn package` , 那么在进行打包之前 , 也会先进行compile和test ( 默认不会进行clean , 如果需要先清理 , 那么可以执行 `mvn clean package` )
#### 使用插件
maven提供了可扩展的插件机制 , 除了官方提供的插件之外 , 还有很多第三方开发的插件
这里使用一个官方提供的`source`插件作为示例
使用这个插件可以把项目的源码进行打包
配置
```xml
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<outputDirectory>打包输出的绝对路径</outputDirectory>
<finalName>文件名</finalName>
<attach>false</attach>
</configuration>
</plugin>
</plugins>
</build>
...
</project>
```
说明 : 最终输出的文件名是`文件名-source.jar`
执行源码打包的命令
```bash
#打包项目源码包(main目录当中的所有内容)
mvn source:jar
#打包测试源码包(test目录当中的所有内容)
mvn source:test-jar
```
##### 将插件绑定到maven的生命周期
在插件的配置当中加入对maven生命周期的绑定
比如说绑定到package生命周期
那么在执行到该生命周期的时候 , 就会对源码进行打包
```xml
<plugin>
...
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
...
</plugin>
```

View File

@ -0,0 +1,85 @@
---
title: Maven(4)-补充
date: 2018-5-9 20:29:31
tags:
- maven
categories:
- Java
---
maven默认使用的JDK版本是1.5 , 我们可以在配置文件中`<profiles>`标签里加入如下内容 , 将默认JDK改为1.8
<!-- more -->
```xml
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
```
---
#### 依赖传递
不同的jar包之间可能存在相对复杂的依赖关系
比如A依赖于B , B依赖于C
那么A就是同时依赖于B和C , 这就是依赖传递
如果在A当中只是用到了B当中的部分内容 , 并不需要依赖于C
那么我们可以将C排除
```xml
<dependency>
<groupId>B-group</groupId>
<artifactId>B</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>C-group</groupId>
<artifactId>C</artifactId>
</exclusion>
</exclusions>
</dependency>
```
这样就可以把对C的依赖排除
#### 依赖冲突
由于jar包可以有不同的版本 , 所以在依赖的关系当中就可能出现依赖的冲突
比如A依赖于B , B依赖于C的2.0版本
A依赖于D , D依赖于E , E依赖于C的2.1版本
那么在A当中实际引入的C , 就会出现冲突
maven在处理这种冲突的时候 , 有以下的原则
1. **最短路径优先** - 也就是在依赖链当中到达该jar包的最短路径 , 比如在上面的例子当中 , 显然到达C的2.0版本的路径较短 , 所以A最终引入的就是C的2.0版本
2. **先声明的优先** - 在路径长度相同的情况下 , 根据在pom.xml当中声明的先后顺序 , 优先使用先声明的
#### 变量的声明与使用
对于一个框架 , 比如spring , 要在项目当中使用需要添加多个依赖包
我们需要对这些依赖包指定统一的版本 , 避免版本不一致出现的问题
这种情况下可以在pom.xml当中声明一个公共的变量
比如
```xml
<properties>
<spring-version>4.3.11.RELEASE</spring-version>
</properties>
...
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-version}</version>
</dependency>
</dependencies>
```
这样就更加清晰 , 也为统一的修改创造了方便

View File

@ -0,0 +1,116 @@
---
title: Maven(5)-jetty-plugin
date: 2018-5-9 20:32:31
tags:
- maven
categories:
- Java
---
与tomcat类似 , jetty也是一个servlet容器 , 为例如jsp和servlet提供运行环境
这里我们使用`jetty-maven-plugin`来部署运行一个web项目
关于如何把普通的maven项目改造为web项目 , 可以参考 [Maven(2)-搭建web项目][web_url]
<!-- more -->
在之前 , 我们仍然需要把这个项目去发布到tomcat然后运行tomcat
这里使用插件来实现把jetty嵌入到项目当中
#### 配置插件
首先需要在pom.xml当中配置这个插件
```xml
<build>
...
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.7.RC0</version>
<configuration>
<jettyXml>jetty.xml</jettyXml>
<webAppSourceDirectory>src/main/webapp</webAppSourceDirectory>
<scanIntervalSeconds>3</scanIntervalSeconds>
<contextPath>/</contextPath>
</configuration>
</plugin>
</plugins>
...
</build>
```
#### 创建jetty配置文件
上面的配置指定了jetty.xml作为jetty的配置文件
当然如果配置比较简单 , 也可以直接写在这个插件的`<configuration>`当中
jetty.xml ( 直接放在项目的根目录下 )
```xml
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Arg name="threadpool">
<New id="threadpool" class="org.eclipse.jetty.util.thread.QueuedThreadPool">
<Arg name="minThreads" type="int">10</Arg>
<Arg name="maxThreads" type="int">200</Arg>
<Arg name="idleTimeout" type="int">60000</Arg>
<Set name="detailedDump">false</Set>
</New>
</Arg>
<Call name="addBean">
<Arg>
<New class="org.eclipse.jetty.util.thread.ScheduledExecutorScheduler"/>
</Arg>
</Call>
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
<Set name="secureScheme">https</Set>
<Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
<Set name="outputBufferSize">32768</Set>
<Set name="requestHeaderSize">8192</Set>
<Set name="responseHeaderSize">8192</Set>
<Set name="sendServerVersion">true</Set>
<Set name="sendDateHeader">false</Set>
<Set name="headerCacheSize">512</Set>
</New>
<Call name="addConnector">
<Arg>
<New class="org.eclipse.jetty.server.ServerConnector">
<Arg name="server"><Ref refid="Server" /></Arg>
<Arg name="factories">
<Array type="org.eclipse.jetty.server.ConnectionFactory">
<Item>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config"><Ref refid="httpConfig" /></Arg>
</New>
</Item>
</Array>
</Arg>
<Set name="host"><Property name="jetty.host" /></Set>
<Set name="port"><Property name="jetty.port" default="8080" /></Set>
<Set name="idleTimeout">30000</Set>
</New>
</Arg>
</Call>
<Set name="stopAtShutdown">true</Set>
<Set name="stopTimeout">5000</Set>
<Set name="dumpAfterStart">false</Set>
<Set name="dumpBeforeStop">false</Set>
</Configure>
```
#### 启动运行
直接在项目目录下执行命令
```bash
mvn jetty:run -e
```
加上`-e`参数 , 如果运行有报错会在控制台打印堆栈信息
之后项目启动成功 , 就可以在浏览器当中根据配置的端口访问了
![maven-jetty-plugin](/images/Java/maven-jetty-plugin.png)
[web_url]: /Java/Maven(2)-搭建web项目/

View File

@ -0,0 +1,49 @@
---
title: Maven(6)-可运行jar打包
date: 2018-5-9 20:42:27
tags:
- maven
categories:
- Java
---
要让jar包是可运行的
也就是可以执行`java -jar demo.jar`来直接运行
需要满足两个条件
1. 依赖的其他jar包也被一同打包进去
2. jar包当中具备清单文件 , 指定运行的主类
<!-- more -->
在maven项目当中 , 可以借助`maven-shade-plugin`来实现
pom.xml
```xml
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.main.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
...
<plugins>
...
</build>
```
这里的`mainClass`需要指定运行的主类是哪个
之后执行`mvn clean package`打包出的jar包 , 就是直接可运行的jar包了

View File

@ -0,0 +1,175 @@
---
title: 自增的线程安全问题
date: 2018-5-9 20:48:56
tags:
- 线程
categories:
- Java
---
Java中的 `i++` 操作也是有可能存在线程安全问题的
如果i是方法内的局部变量 , 则一定是线程安全 , 因为每个方法栈是线程私有的
若i是多个线程可见的变量 , 则存在线程安全问题
<!-- more -->
原因 : 这个操作不是原子性操作 , 在内存中的执行是分为3步的 , `读值` -> `+1` -> `写值`
在这3步之间都可能会有CPU调度产生 , 造成值被修改 , 造成脏读脏写
```java
private static int num = 0;
private static void cnt() {
for(int i=0 ; i<100 ; i++) {
num ++;
}
}
public static void main(String[] args) {
List<Thread> thList = new ArrayList<Thread>();
for(int i=0 ; i<10 ; i++) {
Thread thread = new Thread(){
@Override
public void run() {
try {
//由于自增操作的运算速度要远快于创建和启动线程
//执行一个等待去保证所有的子线程都构造完成以后再执行自增操作
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
cnt();
}
};
thread.start();
thList.add(thread);
}
while(true) {
boolean flag = true;
//判断是否所有子线程都已结束
for(Thread thread : thList) {
flag = flag && !thread.isAlive();
}
if(flag) {
break;
}
}
System.out.println(num);
}
```
以上程序的执行结果多数情况都不是1000 , 虽然num++确实被执行了1000次
这就是不同线程之间的脏读和脏写造成的
----
#### volatile
这是Java当中的一个关键字 , 它的作用概括来说有两点 : `可见性``有序性`
+ **可见性 :** 当一个线程修改了这个变量的值以后 , 其他线程在尝试读取这个值的时候看到的一定是新值
因为运算的操作只有在CPU当中才能执行 , 使用volatile修饰的变量在执行修改以后会立即写入内存 , 而其他线程需要读取值的时候也一定会直接去内存当中读取
反映到硬件层的话 , 就是cpu中的缓存无效
> 从上面的例子来说就是 **+1** 和 **写值**两个操作之间不会被其他线程中断
+ **有序性 : **禁止进行指令重排序
比如如下代码
```java
int i=0;
boolean flag = false;
i = 1;//语句1
falg = true;//语句2
```
在这种情况下 , 并不能保证语句1一定在语句2之前执行 , 可能会发生指令重排序
处理器为了提高程序运行效率 , 可能会对输入代码进行优化
当然这种优化的前提是优化以后程序的执行结果和按照代码顺序执行的结果是一致的
显然上述代码当中先执行语句1还是语句2都是没问题的( 语句2并不依赖语句1的执行结果 ) , 最终结果也没有差别
在单线程中 , 我们通常不会去关注这个问题 , 因为不会对程序的正常执行造成任何影响 , 但是多线程就不同了
```java
//线程A
context = loadContext();//语句1
inited = true;//语句2
//线程B
while(!inited) {
sleep();
}
doSomething(context);
```
如果只看线程A , 那么语句1和语句2的执行先后并没有什么差别
但是如果线程A真的先执行了语句2 , 然后cpu调度切换到了线程B , 那么doSomething就会报错 , 因为此时context还没有被初始化
> 指令重排序不会影响单线程的执行 , 但是会影响到线程并发执行的正确性
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现加入volatile关键字时会多出一个lock前缀指令”
------摘自<深入理解Java虚拟机>
  lock前缀指令实际上相当于一个内存屏障也称内存栅栏内存屏障会提供3个功能
  1它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置也不会把前面的指令排到内存屏障的后面即在执行到内存屏障这句指令时在它前面的操作已经全部完成
  2它会强制将对缓存的修改操作立即写入主存
  3如果是写操作它会导致其他CPU中对应的缓存行无效。
----
#### 原子性
如果一个操作是原子性的 , 就代表它是不可分割的 , 无法被其他线程中断介入的
可以与事务类比
在Java当中 , 对**基本数据类型变量**的`读取``赋值`是原子性操作
但是表现在代码当中 , 是十分不易区分的
例如
```java
x = 10;//语句1
y = x;//语句2
x++;//语句3
x = x+1;//语句4
```
在上述的4个语句当中 , 其实只有语句1是原子性的操作
语句2包含读取x的值和赋值给y两个操作
3和4则包含读取值 , 进行运算 , 赋值操作
根据上述`volatile`的作用 , 可见volatile并**不能**保证原子性
对于开始的程序 , 即使用volatile修饰num , 最终结果也可能不是1000
---
#### Java中的锁机制
运用synchronized或者ReentrantLock都可以解决这个问题
+ 使用synchronized
```java
private static int num = 0;
private static Object obj = new Object();
private static void cnt() {
for(int i=0 ; i<100 ; i++) {
synchronized (obj) {
num ++;
}
}
}
```
+ 使用ReentrantLock
```java
private static int num = 0;
private static ReentrantLock objLock = new ReentrantLock();
private static void cnt() {
for(int i=0 ; i<100 ; i++) {
objLock.lock();
num ++;
objLock.unlock();
}
}
```
---
#### AtomicInteger
这个类的实例表示可以用原子方式更新的int值
调用这个类中的可用方法进行自增操作 , 可以保证线程安全
```java
private static AtomicInteger num = new AtomicInteger(0);
private static void cnt() {
for(int i=0 ; i<100 ; i++) {
num.getAndIncrement();//以原子方式获取并且自增1
}
}
```
>传统的锁机制需要陷入内核态造成上下文切换但是一般持有锁的时间很短频繁的陷入内核开销太大所以随着机器硬件支持CAS后JAVA推出基于compare and set机制的AtomicInteger实际上就是一个CPU循环忙等待。因为持有锁时间一般较短所以大部分情况CAS比锁性能更优。

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB