鹤舞云端

鹤舞云端


  • 首页

  • 标签

  • 分类

  • 归档

Effective Java 笔记8,避免使用Finalizer和Cleaner

发表于 2018-08-14 | 分类于 编程语言

1 被遗弃的finalize()

finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。但是在Java9中以及被标记为deprecated,取而代之的的是Cleaner对象。

finalizer通常是不可预测,经常是危险的,降低性能且不必要的,当然Cleaner虽然好一些,但是也是不可预测地、慢地、通常非必要地

2 避免使用finalize()或者Cleaner的理由

2.1 执行时间没有保证

在对象变为不可达状态和finalizer或者Cleaner执行之间的时间有可能任意长。这意味着不要在finalizer或者Cleaner里执行任何时间敏感的操作,比如关闭文件。

finalizer或者Cleaner执行的迅速性与垃圾回收算法有关,依其实现而各异。

给一个类提供finalizer可能会任意久地延迟其实例的回收。finalizer的执行线程的优先级很低,最终可能会造成应用有上千个对象在它的finalizer队列里等待被回收。

不仅不能保证finalizer或者Cleaner及时执行,甚至根本不能保证其执行。因此,你永远不要依赖一个finalizer或者Cleaner去更新持久化状态。比如依赖finalizer或者Cleaner释放一个共享状态的持久锁(如数据库)是使整个分布式系统停止运行的好方法^-^~~

2.2 uncaught exception在finalization中会被忽略

finalizer的另一个问题是会忽略在finalization中抛出的uncaught exception,然后终止该对象的finalization。未捕获异常可能导致其它对象处于损坏状态。如果另一个线程试图使用这个损坏的对象,可能会导致任意的不确定性行为。

Cleaner没有这个问题,因为一个使用cleaner的库能够控制它自己的线程。

2.3 使用finalizer或者Cleaner会严重影响性能

有时会比try-with-resources方式慢上几十倍。主要因为他们抑制(inhibit)垃圾回收的效率。当然,在你把Cleaner只当做safety net使用时它会快很多。

2.4 安全问题

finalizer会把你的类置于finalizer攻击下。

finalizer攻击的原理很简单:如果有异常从构造函数或者它的序列化中抛出-如readObject和readResolve方法-恶意子类的finalizer可以运行在被应该死掉(died on the
vine)的、只完成部分构造的对象上。这个finalizer可以把对象的引用记录到一个static域上,从而阻止其被GC回收。然后缺陷对象的本不应该存在的方法就会被子类随意调用。

一个final类型的class能够免于出现上述情况,因为不能创建其子类。

解决办法

在对象上写一个final的finalize()方法,这个方法什么也不做

3. finalizer或者Cleaner的替代方法

在不使用finalizer或者Cleaner时,一个类的对象封装了需要回收的资源如文件和或者线程时,我们应该怎样做呢?让这个类实现AutoCloseable,并且要求其的客户端(Client)在不再需时调用每个实例的close()方法。通常使用try-with-resources来保证终止,即使要面对异常。

有一个需要强调的点:实例必须跟踪它是否被closed: 一个对象失效时,close()方法必须记录到一个对象的属性中,其他方法必须检查这个属性,当对象关闭之后被调用时,这些方法应该抛出一个IllegalStateException

4. finalize()和Cleaner的几个应用

4.1 充当safety net的角色

当自由拥有者忘记调用close()方法时,充当保护网(safety net)的角色。虽然不能保证其运行时机,但是在client忘记时,迟些释放自由总比不释放要强。

当你要写这种safety net时要三思,考虑为这些保护付出的代价是否值得。

一些Java类比如FileInputStream,FileOutputStream,ThreadPoolExecutor,java.sql.Connection实现了这样的安全网。

4.2 另一个合法应用

  • cleaners还可用于有本地对等类(native peer)的情况。因为一个native peer不是一个普通的对象,垃圾回收器意识不到其存在,当它的Java peer被回收的时候并不能同时回收它。

  • 当性能是可以接受,并且native peer持有的不是关键资源时,finalizer或者Cleaner是一种完成回收任务的一个合适选项。

  • 当上述性能不能被接受,或者类的native peer持有的是关键资源时需要尽快被回收时,这个类还是应该实现close()方法
    4.2.1 native peer
    native peer是普通对象通过本地方法委托的本地(非java)对象

结论

不要使用cleaners,或者在早于Java9的版本中使用finalize(),除非作为safety net或者终止不重要的本地资源。即使这样,依然要注意不确定性和性能影响。

参考

Effective Java 第三版——8. 避免使用Finalizer和Cleaner机制

java四种引用总结

发表于 2018-08-13 | 分类于 编程语言

对象可达性

  • 主流商用程序语言如java,C#等都是通过“可达性”来判断对象是否存活的。这个算法的基本思路是通过一系列的“GC Roots”对象作为起始点,从这些节点开始搜索,搜索走过的路径称为“引用链”,当一个对象到达GC Roots没有任何引用链相连时,则证明此此对象时不可用的。

    Java中可作为GC Roots的对象包括下面几种

  1. 虚拟机栈中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法中JNI(即一般说的Native方法)引用的对象

Java中的四种引用

  1. 强引用(Strong Reference)
  2. 软引用(SoftReference)
  3. 弱引用(WeakReference)
  4. 虚引用(PhantomReference)

强引用

也就是我们最常见的普通对象的引用,只要还有引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。

软引用

比强引用相对弱化,JVM会确保在OutOfMemory之前,清理软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉。这样就保证了使用缓存的同时,不会耗尽内存。

弱引用

比软引用更弱化,只剩弱引用关联的对象只能生存到下一次垃圾回收之前。无论内存是否足够都会回收掉。

虚引用

对对象的垃圾回收没任何影响,GC根本不考虑它。。。一般用来在被回收时接收一个系统通知。

参考

  1. 《深入理解JVM》3.2

Junit最佳实践(译)

发表于 2018-08-13 | 分类于 测试

原文

JUnit Best Practices

本文针对单元测试的最佳实践目标:

  1. 非常快- 你会写很多的单元测试,而且他们会被频繁的运行,所以他们需要运行的很快。我说的是秒级而非毫秒级别的快。
  2. 非常可靠- 你希望通过测试找到production代码的问题。当且仅当production代码被破坏时,测试才失败。有些人认为测试间歇性失败比不测试更问题更严重。

1. 保证单元测试只在内存中进行

比如,不要在单元测试中发起HTTP请求、访问数据库、或者读取文件系统。这些行为太慢而且不可靠,所以最好把他们留给其他类型的测试,比如功能测试

读取文件系统的测试对单元测试来说太复杂了:

  1. 它们需要配置当前的工作目录的位置,要想在开发机器和构建机器上都做好是比较复杂的。
  2. 它们通常需要存储在源码管理中的文件,并且保持源码管理中这些文件实时更新是很复杂的。

2. 不要跳过单元测试

有许多方式跳过单元测试,但是你不应该这么做:

  • 不要使用Junit的@Ignore注解
  • 不要使用Maven的maven.skip.test属性
  • 不要使用Maven Surefire插件的skipTests属性
  • 不要使用Maven Surefire插件的excludes属性
    被跳过的单元测试不能提供任何好处,但是仍然会被从源码管理中检出和编译。对于不需要的测试,我们应该从源码管理中移除,而不是跳过测试。

3. 针对每个单元测试方法只执行一个断言

  • 当测试失败时,更容易判断是哪里出了问题。假如一个单元测试有三个断言,我们需要进一步尝试去判断到底是哪个断言失败了。
  • 当一个单元测试执行多个断言时,不能保证每个断言都发生。比如一个unchecked exception发生,在这个异常之后的所有断言就都不会再出现了,Junit会把这个方法标记为有一个错误然后执行下一个测试方法。

4. 尽可能使用最强的断言

没有最强断言,单元测试除了覆盖其他什么都做不了。覆盖率是单元测试的有效结果,但我们可以做得更好!特别是,我们希望我们的单元测试确保我们的生产代码正常工作。如果没有强有力的断言,我们的单元测试只能确保我们的生产代码不会在我们面前爆炸。

按照强度递减的顺序,断言分属于以下级别:

  • strongest assertions - 断言方法的返回值
  • strong assertions - 验证与重要依赖的模拟对象交互的正确性
  • weak assertions - 验证与非重要依赖的模拟对象交互的正确性

5. 不要验证与一个模拟logger的交互,除非日志对被测方法至关重要

6. 使用最合适的断言方法

  • 使用 assertTrue(classUnderTest.methodUnderTest()) 而非 assertEquals(true, classUnderTest.methodUnderTest())
  • 使用 assertEquals(expectedReturnValue, classUnderTest.methodUnderTest())而非assertTrue(classUnderTest.methodUnderTest().equals(expectedReturnValue))
  • 使用assertEquals(expectedCollection, classUnderTest.getCollection()),而非断言集合的大小和每个元素。

7. 断言参数顺序要合适

比如Junit的断言参数有:

  1. expected
  2. actual
    则应使用assertEquals(expected, actual)而非assertEquals(actual, expected)。断言参数顺序的准确性可以确保JUnit消息的准确性。

8. 使用模拟框架(mock Framework)时应使用准备匹配

9. 命名一个单元测试时,使用包含被测方法和条件的惯例

我们项目中的惯例是:methodUnderTest_condition
比如:

  • encode_nullBytes
  • encode_emptyBytes
  • encode_tooFewBytes
  • encode_tooManyBytes
  • encode_rightNumBytes or encode_validBytes

10. 确保测试类与被测试的生产代码类在同一个包下

当与被测试的生产类位于同一个包中时,测试类可以使用包私有类并调用包私有方法。根据我的经验,高质量的代码库广泛使用包私有类和方法来隐藏实现细节。

11. 确保测试代码与生产代码分开

Maven项目中的默认文件夹结构这样做:
生产代码存在于src / main / java文件夹中
测试代码存在于src / test / java文件夹中。
即使您不使用Maven,也请将测试代码和生产代码放在不同的文件夹中

12. 不要在单元测试中打印任何东西

13. 不要在单元测试类构造函数中初始化,请改用@Before方法

如果在测试类构造函数期间发生故障,则会发生AssertionFailedError,并且堆栈跟踪的信息量不大;特别是,堆栈跟踪不包括原始错误的位置。另一方面,如果在@Before方法期间发生故障,则可以获得有关故障位置的所有详细信息。

有些团队允许在构造函数中初始化测试类成员,当这些成员是一个简单类型(如String)时,但我个人不会初始化测试类成员。相反,我总是做以下其中一项:

  • 在使用@Before(首选)注释的方法中为测试类成员分配值
  • 将简单值分配给测试类中的final static成员变量

14.不要在测试类中使用静态成员

静态成员使单元测试方法产生依赖。不要使用它们!相反,努力编写完全独立的测试方法

15. 不要自己编写为了让测试失败时才存在的catch块

没必要编写自己的catch块只是为了应对测试失败,因为JUnit框架会为您处理这种情况。例如,假设您正在为以下方法编写单元测试:

1
2
3
4
final class Foo
{
int foo(int i) throws IOException;
}

这里我们有一个方法接受一个整数并返回一个整数,并在遇到错误时抛出IOException。这是编写单元测试的错误方法,该测试确认方法在传递7时返回3

1
2
3
4
5
6
7
8
9
10
11
12
13
// Don't do this - it's not necessary to write the try/catch!
@Test
public void foo_seven()
{
try
{
assertEquals(3, new Foo().foo(7));
}
catch (final IOException e)
{
fail();
}
}

正在测试的方法指定它可以抛出IOException,这是一个经过检查的异常。因此,除非您捕获异常或声明测试方法可以传播异常,否则单元测试将无法编译。第二种替代方案是优选的,因为它导致更短和更集中的测试

1
2
3
4
5
6
// Do this instead
@Test
public void foo_seven() throws Exception
{
assertEquals(3, new Foo().foo(7));
}

16. 不要编写自己的catch块,只是为了通过测试

不要这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Don't do this - it's not necessary to write the try/catch!
@Test
public void foo_nine()
{
boolean wasExceptionThrown = false;
try
{
new Foo().foo(9);
}
catch (final IOException e)
{
wasExceptionThrown = true;
}
assertTrue(wasExceptionThrown);
}

应该:

1
2
3
4
5
6
// Do this instead
@Test(expected = IOException.class)
public void foo_nine() throws Exception
{
new Foo().foo(9);
}

17. 不要编写自己的catch块,仅用于打印堆栈跟踪

1
2
3
4
5
6
7
8
9
10
11
12
13
// Don't do this - it's not necessary to write the try/catch!
@Test
public void foo_seven()
{
try
{
assertEquals(3, new Foo().foo(7));
}
catch (final IOException e)
{
e.printStackTrace();
}
}

应该:

1
2
3
4
5
6
// Do this instead
@Test
public void foo_seven() throws Exception
{
assertEquals(3, new Foo().foo(7));
}

18. 在测试类中,不要声明方法抛出任何特定类型的异常

1
2
3
4
5
6
// Don't do this - the throws clause is too specific!
@Test
public void foo_seven() throws IOException
{
assertEquals(3, new Foo().foo(7));
}

这种测试方法很脆弱。想象一下,测试中的方法发生了变化,导致它抛出IOException或GeneralSecurityException。在这种情况下,我们必须更改它的测试方法进行编译
所以我们应该

1
2
3
4
5
6
// Do this instead
@Test
public void foo_seven() throws Exception
{
assertEquals(3, new Foo().foo(7));
}

19. 不要在单元测试中使用Thread.sleep

当单元测试使用Thread.sleep时,它不能可靠地指示生产代码中的问题。例如,这样的测试可能会失败,因为它在比平常慢的机器上运行。目标是当且仅当生产代码被破坏时才会失败的单元测试.

不应该在单元测试中使用Thread.sleep,而是重构生产代码以允许注入模拟对象,该模拟对象可以模拟通常必须等待的可能长时间运行的操作的成功或失败

20. 不要尝试测试直接或间接调用Thread.sleep的生产方法的时间

同上

21. 直接测试类;不要依赖间接测试

java8时间相关类笔记

发表于 2018-08-13 | 分类于 编程语言

所在包

Java8引入了一套全新的时间日期API,在java.time包下。

time包中的是类是不可变且线程安全的

主要类

  • Instant ——它代表的是时间戳
  • LocalDate——不包含具体时间的日期,比如2014-01-14。它可以用来存储生日,周年纪念日,入职日期等。
  • LocalTime——它代表的是不含日期的时间
  • LocalDateTime——它包含了日期及时间,不过还是没有偏移信息或者说时区。
  • ZonedDateTime——这是一个包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的。

参考

java8新的时间日期库及使用示例

docker笔记1

发表于 2018-08-08 | 分类于 项目管理

Docker是什么

  • Docker 是一个执行操作系统级别虚拟化的软件(operating-system-level virtualization),此技术也被成为容器化(containerization),简而言之就是一个可以运行应用的容器。
  • Docker 是一个开源项目,诞生于 2013 年初,最初是 dotCloud 公司内部的一个业余项目。它基于 Google 公司推出的 Go 语言实现。 项目后来加入了 Linux 基金会,遵从了 Apache 2.0 协议,项目代码在 GitHub 上进行维护。

    容器(Container)是什么

  • 容器是将需要的软件打包的一个标准单元。
  • 一个Docker容器镜像是一个轻量级的、独立的、可执行的软件包,包中包括应用运行所需要的一切:代码、运行时、系统统计、系统类库和设置。

    容器的运行机制

  • 容器镜像在运行时就成为一个容器,具体到Docker的情形就是: 当镜像在Docker Engine上运行时就成为了容器。
  • 无论基础架构如何,容器化软件都能始终运行如一,且适用于基于Windows和Linux的应用。

Docker能做什么

Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案。 Docker 的基础是 Linux 容器(LXC)等技术。在 LXC 的基础上 Docker 进行了进一步的封装,让用户不需要去关心容器的管理,使得操作更为简便。用户操作 Docker 的容器就像操作一个快速轻量级的虚拟机一样简单

Docker的优势是什么

Docker主要是用来代替传统虚拟化方式(虚拟机)技术的,有以下特点

  • 容器是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统,而传统方式则是在硬件层面实现
  • 更快速的交付和部署
  • 高效的部署和扩容
  • 更高的资源利用率
  • 更简单的管理

Docker核心概念

镜像(images)

Docker 镜像(Image)就是一个只读的模板。镜像可以用来创建 Docker 容器,一个镜像可以创建很多容器。Docker 提供了一个很简单的机制来创建镜像或者更新现有的镜像,用户甚至可以直接从其他人那里下载一个已经做好的镜像来直接使用

仓库(repository)

仓库(Repository)是集中存放镜像文件的场所。有时候会把仓库和仓库注册服务器(Registry)混为一谈,并不严格区分。实际上,仓库注册服务器上往往存放着多个仓库,每个仓库中又包含了多个镜像,每个镜像有不同的标签(tag)。

仓库分为公开仓库(Public)和私有仓库(Private)两种形式。最大的公开仓库是 Docker Hub,存放了数量庞大的镜像供用户下载。国内的公开仓库包括 时速云 、网易云 等,可以提供大陆用户更稳定快速的访问。当然,用户也可以在本地网络内创建一个私有仓库。

当用户创建了自己的镜像之后就可以使用 push 命令将它上传到公有或者私有仓库,这样下次在另外一台机器上使用这个镜像时候,只需要从仓库上 pull 下来就可以了。

Docker 仓库的概念跟 Git 类似,注册服务器可以理解为 GitHub 这样的托管服务。

参考

  1. 几张图帮你理解 docker 基本原理及快速入门
  2. Docker维基百科)
  3. Docker官网:What is a Container
  4. Docker —几个概念的理解

maven私服搭建

发表于 2018-07-31 | 分类于 项目管理

1. 私服搭建的意义

  1. 是为了进行仓库的管理和控制,以及减少对中心仓库的访问,减少网络请求提高效率。
  2. 以及为内网用户在无法访问外网却需要使用maven功能
  3. 发布自己的组件到私服

2. 所用工具Nexus Repository Manager (NXRM)

NXRM是一个仓库管理工具,能够实现上节所提到的三种特性,它不仅支持maven还支持其他多种仓库形式:

Repository Formats:

  • Maven Repositories with Apache Maven and Other Tools
  • .NET Package Repositories with NuGet
  • Private Registry for Docker
  • Node Packaged Modules and npm Registries
  • Bower Repositories
  • PyPI Repositories
  • Ruby, RubyGems and Gem Repositories
  • Raw Repositories, Maven Sites and More
  • Git LFS Repositories
  • Yum Repositories

另外,NXRM还提供有web用户界面。

3. NXRM服务器的搭建

NXRM支持*unix,OSX,windows,Docker等多种平台。最新版本是Nexus Repository Manager (NXRM) 3,此次使用的平台及版本为nexus-3.13.0-01-unix。

3.1 linux下安装

此次安装和配置主要参考文章:Linux 使用 Nexus3.x 搭建 Maven 私服指南

在安装之前首先参考如下不同之处:

3.1.1 其中所用版本略有不同
  1. 下载页面
  2. 选择nexus-3.13.0-01-unix版本,进行下载
3.1.2 默认8081端口修改
  1. 将$NEXUS_HOME/nexus-3.13.0-01/etc/nexus-default.properties中的application-port=8081修改为想要的端口,如application-port=9001

maven非标准结构处理

发表于 2018-07-30 | 分类于 项目管理

maven build配置

1. 非标准maven结构指定test目录

1
2
3
4
5
6
7
8
9
<build>
//...
<directory>target</directory> // 工作目录
<sourceDirectory>src</sourceDirectory> // 源码目录
<scriptSourceDirectory>js/scripts</scriptSourceDirectory> //脚本目录
<testSourceDirectory>test</testSourceDirectory> // 测试目录
<outputDirectory>target/classes</outputDirectory> // 源码编译目录,被工作目录包含
<testOutputDirectory>target/test/classes</testOutputDirectory>// 测试源码编译目录,被工作目录包含
</build>

2. 生成类的单元测试类到指定路径

idea生成类的单元测试类到指定路径

3. 怎么打包其他的非标准目录。如某个项目根目录下的my-resource资源目录

1
2
3
4
5
6
7
8
9
10
11
<build>
...
<resources>
<resource>
<targetPath>my-resource</targetPath>
<directory>my-resource</directory>
<filtering>true</filtering>//此选项是用来替换变量的,参考代码后的文档
</resource>
</resources>
...
</build>

maven resource filtering

maven之生命周期

发表于 2018-07-27 | 分类于 项目管理

maven生命周期

Maven将项目的清理、编译、测试、部署等构建过程的各个阶段进行了抽象,形成了三个相互独立的生命周期:

clean生命周期

主要负责项目的清理工作。包括以下几个顺序阶段:

  1. pre-clean :执行清理前的工作;
  2. clean :清理上一次构建生成的所有文件;
  3. post-clean :执行清理后的工作

default生命周期

负责项目的部署,default生命周期是最核心的,它包含了构建项目时真正需要执行的所有步骤。它包含以下各个阶段:

  1. validate
  2. initialize
  3. generate-sources
  4. process-sources
  5. generate-resources
  6. process-resources :复制和处理资源文件到target目录,准备打包;
  7. compile :编译项目的源代码;
  8. process-classes
  9. generate-test-sources
  10. process-test-sources
  11. generate-test-resources
  12. process-test-resources
  13. test-compile :编译测试源代码;
  14. process-test-classes
  15. test :运行测试代码;
  16. prepare-package
  17. package :打包成jar或者war或者其他格式的分发包;
  18. pre-integration-test
  19. integration-test
  20. post-integration-test
  21. verify
  22. install :将打好的包安装到本地仓库,供其他项目使用;
  23. deploy :将打好的包安装到远程仓库,供其他项目使用;

site生命周期

负责创建项目的文档站点等工作。

  1. pre-site
  2. site :生成项目的站点文档;
  3. post-site
  4. site-deploy :发布生成的站点文档

执行方式解释

每个生命周期都是按顺序执行,且后面的phase依赖于前面的phase。执行某个phase时,其前面的phase会依顺序执行,即mvn cmd指定的某个cmd阶段为从该生命周期顺序执行构建过程的最后一个阶段,但不会触发另外两套生命周期中的任何phase

测试

约定优于配置:

在默认情况下,“maven-surefire-plugin”插件将自动执行项目“src/test/java”路径下的测试类,但测试类需要遵从以下命名模式,Maven才能自动执行它们:  

  • Test*.java:以Test开头的Java类;
  • *Test.java :以Test结尾的Java类;
  • *TestCase.java:以TestCase结尾的Java类;

参考文章

  • Maven测试篇
  • Maven入门指南⑦:Maven的生命周期和插件

redis笔记1

发表于 2018-07-25 | 分类于 NoSql

redis数据结构概览,及部分常用命令

字符串

存储的值 操作 说明
字符串、整数、浮点数 set get del 支持对字符串全部或部分的操作、支持对整数和浮点数的自增、自减操作

列表

命令 行为
RPUSH 将给定值push到列表最右边
LPUSH 将给定值push到列表最左边
LINDEX 获取列表对应位置上的单个元素,左起为0
LPOP 从列表最左边弹出一个元素,并且返回该元素的值

集合(set)

命令 行为
SADD 添加元素到集合,重复元素不会添加
SREM 从集合中删除元素
SISMEMBER 查看元素是否在集合中
SMEMBERS 查看集合中所有元素,如果元素多会很慢,慎用

散列(hash)

命令 行为
HSET 添加元素到hash,如果key重复则覆盖旧的value
HDEL 从hash中删除元素
HGET 读取指定元素的值
HGETALL 查看集合中所有元素

有序集合(zset)

有序集合类似散列也是键值对,不过值是score,而且必须为浮点数

命令 行为 示例
ZADD 添加元素到 ZADD key score member
ZREM 删除元素
ZRANGE 读取指定位置范围的member ZRANGE key start stop [WITHSCORES] ,位置都是闭合的
ZRANGEBYSCORE 读取指定score范围的member ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

数据结构操作进阶

字符串

  1. valued的整数类型的取值范围与系统的长整数一致,32位系统上就是32位有符号整数,在64位系统上就是64位有符号整数。

    发布和订阅

    PUBLISH
    SUBSCRIBE

    Sort

    能同时适用三种数据的操作

    基本的redis事务

    用法:
    MULTI cmd1 cmd2 .. EXEC

    过期

  2. redis不能为键里单个元素设置过期时间。
    |命令 | 行为 |示例|
    |—-|—-|
    |PESIST|移除键的过期时间|
    |TTL|查看键还有多久过期时间,单位是秒|
    |PTTL|查看键还有多久过期时间,单位是毫秒|

Java并发笔记4

发表于 2018-07-17 | 分类于 编程语言

Java编发编程第三章 共享对象

3.1.3 锁与可见性

  • 64位数值(如long/double)在并发时会出现问题

3.1.4 易变变量

  • 加锁的意义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读和写操作的线程都应该在同一个锁上同步。
  • 当一个变量被声明为volatile时,编译器和运行时会注意到这是一个共享变量,并且对它的操作不应该与其他内存操作一起被重新排序。
  • 从内存的角度,写入volatile变量就像是退出了同步块,而读取一个volatile变量就像进入了同步块。
  • 不建议过度依赖volatile变量提供的可见性,这通常比使用锁更脆弱而且更难以理解。
  • 只在能够简化代码实现和并发策略验证时使用volatile,如果验证正确性时需要对可见性进行微妙推理时,就要避免使用volatile。
  • volatile的正确使用方法包括:1.保证自身可见性。2.保证他们指向对象的可见性。3.指示重要声明周期(初始化或者关闭)事件的发生。
  • 调试提示: 对于服务器程序,无论是开发还是测试阶段启动JVM都一定要指定-server选项
    典型应用
    使用volatile变量作为标志,只是循环的结束。
    最佳实践
    当且仅当满足以下条件时,才应该使用volatile变量
  1. 对变量的写入不依赖于变量的当前值,或者你能保证只有单个线程更新变量的值。
  2. 该变量不会与其他变量一起纳入不可变性条件当中。
  3. 在访问变量时不需要加锁。

3.2 发布和逸出

逸出: 当发布了一个不该被发布的对象时,这种情况就叫逸出(escape)

3.5 安全发布

不可变对象所需要的三个条件:

  1. 对象的状态不可改变
  2. 所有的field都是final类型
  3. 正确的构造过程
  • 任何线程都可以在不需要额外同步的情况下安全的访问不可变对象,即使在发布这些对象时没有使用同步。
  • 尽管在构造函数中设置的域值貌似是对该值的第一次写入,因此不会有“更旧的”的值被视为失效值,但实际上Object的构造函数会在子对象的构造函数执行前先将所有域设置一遍默认值,因此某个域的值可能被视为失效值。
    3.5.3 安全发布的常用模式
    可变对象必须通过安全方式发布,这通常意味着发布和使用该对象的线程都需要使用同步。
    一个正确构造的对象可以通过以下几种方式安全地被发布:
  • 在静态初始化函数中初始化一个对象的引用
  • 将该对象的引用保存到volatile类型的域或者AtomicReferance对象中
  • 将该对象的引用保存到一个被正确构造对象的final域中 或
  • 将该对象的引用保存到一个由正确的锁保护的域中
3.5.4 事实不可变对象
3.5.5 可变对象

对象得发布需求取决于它的可变性:

  • 不可变对象可以通过任意方式发布
  • 事实不可变对象需要同步安全方式发布
  • 可变对象必须同过安全发布,并且是线程安全的或者由某个锁保护起来

第四章 对象的组合

4.1 设计线程安全的类

在设计线程安全类的过程中,需要包含以下三个基本要素

  • 找出构成对象状态的所有变量
  • 找出约束状态变量的不变性条件
  • 建立对象状态的并发访问策略
123

Willcat

27 日志
11 分类
32 标签
© 2019 Willcat
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4