守护线程

有时候,你希望创建一个线程来执行一些辅助工作,但又不希望这个线程阻碍JVM的关闭。在这种情况下就需要使用守护线程(Daemon Thread)。
线程可分为两种:普通线程和守护线程。在JVM启动时创建的所有线程中,除了主线程以外,其他的线程都是守护线程(例如垃圾回收器以及其他执行辅助工作的线程)。当创建一个新线程时,新线程将继承创建它的线程的守护状态,因此在默认情况下,主线程创建的所有线程都是普通线程。
普通线程与守护线程之间的差异仅在于当线程退出时发生的操作。当一个线程退出时,JVM会检查其他正在运行的线程,如果这些线程都是守护线程,那么JVM会正常退出操作。当JVM停止时,所有仍然存在的守护线程都将抛弃–既不会执行finally代码块,也不会执行回卷栈,而JVM只是直接退出。
我们应尽可能少的使用守护线程–很少操作能在不进行清理的情况下被安全的抛弃。特别是,如果在守护线程中执行可能包含I/O操作的任务,那么将是一种危险的行为。守护线程最好用于执行“内部”任务,例如周期性地从内存缓存中移除逾期的数据。

此外,守护线程通常不能用来代替应用程序管理程序中各个服务的生命周期。

Java中的参数传递——值传递、引用传递

首先,不要纠结于Pass By Value 和 Pass By Reference 的字面上的意义,否则很容易陷入所谓的“一切传引用其本质上是传值”这种并不能解决问题毫无意义论战中。更何况,要想知道Java到底是传值还是传引用,起码你要先知道传值和传引用的准确含义吧?可是如果你已经知道了这两个名字的准确含义,那么你自己就能判断Java到底是传值还传引用。这个好像用大学的名词来解释高中的题目,对于初学者根本没有任何意义。

Java 内存模型

happen-before 规则介绍

Java 语言中有一个“先行发生”(happen-before)的规则,它是Java内存模型中定义的两项操作之间的关系,如果操作A先行发生于B,其意思就是说,在发生操作B之前,操作A产生的影响都能被操作B观察到,“影响”包括修改了内存中的共享变量的值、发送了消息、调用了方法等,它与时间的先后发生基本没有太大关系。z合格原则特别重要,它是判断数据是否存在竞争、线程是否安全的重要以及。

第一部分总结

并发主要概念和规则

  • 可变状态至关重要的
      所有的并发问题都可以归结为如何协调对并发状态的访问。可变状态越少,就越容易确保线程安全性。
  • 尽量将域声明为final类型,除非需要它们是可变的。
  • 不可变对象一定是线程安全的。
      不可变对象能极大的降低并发编程的复杂性。它们更为简单且安全,可以任意共享而无须使用加锁或者保护复制等机制。

同步工具类

闭锁

  闭锁可以延迟线程的进度直到其达到终止状态[CPJ 3.4.2]。闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开并允许所有线程通过。当闭锁到达结束状态后,将不会再改变状态。因此这扇门将永远保持打开状态。闭锁可以用来确保某些活动直到其他活动都完成后才继续执行,例如:

  • 确保某个计算在其需要的所有资源都被初始化之后才继续执行。二元闭锁(包括两个状态)可以用来表示“资源R已经被初始化”,而所有需要R的操作都必须先在这个闭锁上等待。
  • 确保某个服务在其依赖的所有其他服务都已经启动之后才启动。每个服务都有一个相关的二元闭锁。当启动服务S时,将首先在S依赖的其他服务的闭锁上等待,在所有依赖的服务都启动后会释放闭锁S,这样其依赖S的服务才能继续执行。
  • 等待直到某个操作的所有参与者(例如,在多玩家游戏中的所有玩家)都就绪再继续执行。在这种情况中,当所有玩家都准备就绪时,闭锁将到达结束状态。

对象的共享

可见性

当读操作和写操作在不同线程的时候,读到可能是旧的值。为了确保多线程之间对内存的写入操作的可见性,必须使用同步机制。
当线程在没有同步的情况下读取变量时,可能会得到一个失效的值,但至少这个值是由之前某个线程设置的值,而不是随机值。这种安全性保证也被称为最低安全性。
最低安全性适用于大多数变量,但是存在一个例外:非volatile类型的64位数值变量double和long。Java内存模型要求,变量的读写操作都必须原子操作,但是对于非volatile类型的long和double变量,JVM允许将64位的读写操作分解为两个32位的操作。当读取一个非volatile类型的long变量时,如果对该变量的读写操作在不同线程执行,那么很可能会一个线程刚写入高32位,就被另一个线程读到,那么读线程就读到一个新的高32和旧低32位的值。
加锁的意义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程能看到共享变量的最新值,所有执行读写操作的线程都必须在同一个锁上同步。

并发简史

  • 资源利用率: 在一些情况下,程序必须等待某个外部操作执行完成,例如输入操作或者输出操作等,而在等待时程序无法执行其他任何操作。因此,如果在等待的同时可以运行另一个程序,那么无疑将提高资源的利用率。
  • 公平性: 不同的用户和程序对于计算机上的资源有着同等的使用权。一种高效的运行方式是通过粗粒度的时间分片(Tmie Slicing)使这些用户和程序能共享计算机资源,而不是由一个程序从头运行到尾,然后再启动下一个程序。
  • 便利性: 通常来说,在计算多个任务时,应该编写多个程序,每个程序执行一个任务并在必要的时候互相通信,这比只编写一个程序来计算所有任务更容易实现。

同步和Java内存模型 (三)可见性

只有在下列情况时,一个线程对字段的修改才能确保对另一个线程可见:

  • 一个写线程释放一个锁之后,另一个读线程随后获取了同一个锁。
    本质上,线程释放锁时会将强制刷新工作内存中的脏数据到主内存中,获取一个锁将强制线程装载(或重新装载)字段的值。锁提供对一个同步方法或块的互斥性执行,线程执行获取锁和释放锁时,所有对字段的访问的内存效果都是已定义的。
    注意同步的双重含义:锁提供高级同步协议,同时在线程执行同步方法或块时,内存系统(有时通过内存屏障指令)保证值的一致性。这说明,与顺序程序设计相比较,并发程序设计与分布式程序设计更加类似。同步的第二个特性可以视为一种机制:一个线程在运行已同步方法时,它将发送和/或接收其他线程在同步方法中对变量所做的修改。从这一点来说,使用锁和发送消息仅仅是语法不同而已。

同步和Java内存模型 (二)原子性

除了long型字段和double型字段外,java内存模型确保访问任意类型字段所对应的内存单元都是原子的。这包括引用其它对象的引用类型的字段。此外,volatile long 和volatile double也具有原子性 。(虽然java内存模型不保证non-volatile long 和 non-volatile double的原子性,当然它们在某些场合也具有原子性。)(译注:non-volatile long在64位JVM,OS,CPU下具有原子性)

同步与Java内存模型(一)序言

先来看如下这个简单的Java类,该类中并没有使用任何的同步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final class SetCheck {
private int a = 0;
private long b = 0;

void set() {
a = 1;
b = -1;
}

boolean check() {
return ((b == 0) ||
(b == -1 && a == 1));
}
}

|