Java 并发 Concurrency
计算机用户通常期望应用程序能同时执行多项任务。Java 平台 支持并发(concurrency)编程来实现这一效果。
进程和线程 Processes and Threads
并发编程中,有两个基本执行单元:进程和线程。(basic units of execution: processes and threads。主要关心的是线程, 但进程也很重要。
进程
进程是一个自包含的执行环境,具有完整私有的运行时资源和独立的内存空间。
大部分 Java virtual machine 以单进程的方式运行。
线程
线程有时也叫做轻量级的进程。两者都提供了执行环境,但新建线程占用更少的资源。
线程存在于进程中,共享进程的资源,如内存和文件等。这样交互更有效率,但也更容易出问题。
多线程运行是 Java 的核心特性。每个应用从一个主线程(main thread)开始,主线程可以创建更多的附加线程(additional threads)。
线程对象 Thread Objects
每个线程和类 Thread 的一个实例关联。当需要执行异步的任务时,可以实例化 Thread,通过该对象直接创建和管理线程。
定义和启动线程
- implements Runnable / run method
- new Thread(runnableObject)
- t.start()
public class HelloRunnable implements Runnable {
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String args[]) {
(new Thread(new HelloRunnable())).start();
}
}
Thread.sleep 暂停当前的线程
Thread.sleep
可以暂停当前的线程,持续指定时间
// Pause for 4 seconds
Thread.sleep(4000);
join 暂停当前线程,等待另一线程完成
可以指定时间,超时后继续当前线程
t.join(1000);
interrupt 提前中止线程
会触发被中止线程的 InterruptedException
t.interrupt();
// in t run method
try {
} catch (InterruptedException e) {
}
The SimpleThreads Example
public class SimpleThread {
// Display a message, preceded by the name the current thread
static void threadMessage(String message) {
String threadName = Thread.currentThread().getName();
System.out.format("%s: %s%n", threadName, message);
}
private static class MessageLoop
implements Runnable {
public void run() {
String importantInfo[] = {
"Mares eat oats",
"Does eat oats",
"Mares eat lettuce",
"Does eat lettuce",
};
try {
for (int i = 0; i < importantInfo.length; i++) {
Thread.sleep(4000);
threadMessage(importantInfo[i]);
}
} catch (InterruptedException e) {
threadMessage("I wasn't done!");
}
}
}
public static void main(String args[])
throws InterruptedException {
// Delay, in milliseconds before we interrupt MessageLoop thread (default one hour)
long patience = 1000 * 60 * 60;
// If command line argument present, gives patience in seconds
if (args.length > 0) {
try {
patience = Long.parseLong(args[0]) * 1000;
} catch (NumberFormatException e) {
System.err.println("Argument must be an integer.");
System.exit(1);
}
}
threadMessage("Starting MessageLoop thread");
long startTime = System.currentTimeMillis();
Thread t = new Thread(new MessageLoop());
t.start();
threadMessage("Waiting for MessageLoop thread to finish");
// loop until MessageLoop thread exits
while (t.isAlive()) {
threadMessage("Still waiting...");
// Wait maximum of 1 second
// for MessageLoop thread
// to finish
t.join(1000);
if (((System.currentTimeMillis() - startTime) > patience) && t.isAlive()) {
threadMessage("Tired of waiting!");
t.interrupt();
// Shouldn't be long now
// -- wait indefinitely
t.join();
}
}
threadMessage("Finally!");
}
}
Synchronization (同步机制)
线程主要通过共享对象和对象的字段来通信。这回导致两种错误:thread interference
and memory consistency errors
。预防这两种的错误的工具就是 synchronization
。
Thread Interference (线程干扰)
两个线程在同时修改同一对象。修改所包含的步骤的存在覆盖。这会导致修改的结果不可靠。
public class Counter {
private int c = 0;
public void increment() {
c++;
}
public void decrement() {
c--;
}
public int value() {
return c;
}
}
c++
实际包含了三个步骤
- retrieve the value of c
- increment the value by 1
- store the value back in c
Memory Consistency Errors (内存一致性错误)
指不同的线程看到的数据不一致。避免这一错误的关键是理解 happens-before
关系。两个语句存在 happens-before
,表示 特定语句对内存的写入操作对后续的语句可见(生效)。
对于线程内的语句, 以下方法可以保证 happens-before
关系
-
Thread.start 前的语句
-
Thread.join 后的语句
Synchronized Methods (同步方法)
在方法前添加 synchronized
关键字,有以下效果
同一个对象的同步方法
- 只能依次执行
- 存在
happends-before
关系
public class SynchronizeCounter {
private int c = 0;
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
public synchronized int value() {
return c;
}
}
Intrinsic Locks and Synchronization(内部锁和同步)
同步机制建立在一个内部实体的基础上,称为 intrinsic lock
or monitor lock
or simply monitor
。它既能保证排他性的访问对象状态,也能确保 happens-before 关系。
Locks In Synchronized Methods (同步方法的锁)
当线程调用同步方法时,会自动获得对象的内部锁,并在结束时释放。
Synchronized Statements (同步化语句)
同步化语句需要指定提供内部锁的对象。 在语句级别进行同步,可以避免同步同一方法的其他语句,从而避免一些可能的错误。
public void addName(String name) {
synchronized(this) {
lastName = name;
nameCount++;
}
nameList.add(name);
}
Reentrant Synchronization
一个线程可以再次获取已经拥有的锁。也就是可以多次获取同一锁。
Atomic Access (原子化读写)
原子化表示一次性完成的动作。要么完成,要么就不发生。等完成后才产生副作用。
以下行为是原子化的
- 读、写引用型变量和基础变量 (除了
long
anddouble
) - 读、写所有声明为
volatile
的变量 (包括long
anddouble
)
原子化行为不能被拆分和覆盖,可以避免线程之间的干扰。但依然存在内存一致性错误的可能,需要同步原子化行为。
++
是非原子化的,因为可以拆分成多个独立的步骤。