Java-10_2-并发编程
使用 Java 进行并发编程,最好的方案是尽量减少多个进程间的数据共享。如果一定需要共享数据,则推荐使用上一篇中介绍的 Java 类库中线程安全的类进行数据的处理。但为了了解这些 Java 类库中线程安全类的实现方案,也防止我们在极端的时候需要实现自己的线程安全的类,本篇中通过介绍锁,以及 thread 的概念,来了解线程安全的具体机制与底层实现方法。
A. 锁与条件
Java 中可以通过加锁来保证程序的线程安全,通过对 lock 原理机制的了解,我们可以更好的理解线程安全的数据结构,计数器的内部实现。
但由于 lock 的错误使用可能引发的重大程序风险(deadlock),在实际应用中,lock 应当被作为最后的备用选项。我们应当首先尝试避免在不同线程间分享数据,或者使用不可更改(immutable)的数据,或者通过数据的复制进行线程间值的传递。如果必须使用公用的数据,则优先考虑使用 Java 类库提供的线程安全对象和方法。
a. 同步锁的实现
首先通过下面的例子,解释锁的作用流程:
1 | Lock countLock = new ReentrantLock(); // 该对象可以在不同线程中分享使用 |
上述的程序中,我们声明了一个 Lock 类型的对象,在程序中,当其他的线程尝试调用这个 lock 的时候,系统会检查这个锁是否已经被占用,如果被占用,需要进行等待,直到占用锁的进行调用了 unlock()
方法后,才能获得锁并执行锁之后的程序。从而保证了线程的安全。
需要注意的是,unlock
方法一定要保证得到执行,否则程序会阻塞在 lock 调用的位置,造成危险。
在实际的使用中,我们并不显示的使用 lock 对象,而是使用 synchronized
关键字隐式的调用对象的固有锁,例子如下:
1 | // 锁住一部分代码块 |
b. 等待条件
wait()
, notifyAll()
方法为 Object
类的方法,他们都用于带锁的线程程序。当程序调用 wait()
方法时,wait()
所在的线程会交出锁,然后被放置到一个等待集合中(wait set), 并会一直处于等待状态,直到当其他线程中调用了 notifyAll()
方法,wait set中所有的线程会被唤醒,并有机会重新获得程序锁。当重新获得锁后,线程程序会从 wait()
所在的程序子句重新开始执行。 (还有 notify()
方法,只唤醒 wait set 中的一个线程。)
B. 线程
线程是系统任务并发任务执行的载体,通常,我们推荐使用 excutor 来管理 thread。当然,Java 也提供了让设计者自己控制 thread 的方法。
a. 线程的基本操作
通过下述代码,我们看线程的基础启动,睡眠,中断操作。
1 | // 新建 and 启动线程 |
线程的中断机制比较奇怪,实际上并不是真的暂停程序,而是仅去设置一下程序的中断标志位。 (参考) 具体的借助该中断标志位实现线程任务的终止的方法,参考上面的示例程序。
当线程触发中断时,线程正处于 sleep()
or 等待 等情况时,线程会立即被重新激活,并且抛出一个 InterruptedException,但是,这种情况下,中断标志位并没有变化。
Thread 类的 stop()
, suspend()
, resume()
方法,已经不推荐使用。
b. 线程的本地变量与其他性质
线程可以通过 ThreadLocal 辅助类来实现属于每个线程自己的实例变量,具体操作如下:
1 | public static final ThreadLocal<NumberFormat> currencyFormat |
在线程中,当第一次调用 currencyFormat 的get() 方法,lambda 表达式中的方法会被调用,构造一个属于当前线程的实例对象。
线程其他特性
线程可以分组,通常使用 excutor 机制自动管理线程组。
线程可设定优先级,优先级与系统,实现平台有很大关系,可移植性不好,因此不建议使用。
线程有状态,new, running, blocked on input/output 等。
Daemon线程,是一个辅助线程,唯一的工作就是为其他线程提供服务。可以通过 thread.setDaemon(true)
启动该线程。
C. 进程
进程(process)是操作系统中用于运行每一个程序的承载单元,Java 可以通过 Process 类 和 ProcessBuilder 类来配置、构建进行。
下面简单介绍Java 处理进行的方案:
- 构建进程(Build a Process)
1 | // 新建进程,参数中必须要有可执行的指令用于处理后面的文件 |
- 启动、运行进程
1 | // 启动进程 |
Java 可以通过 进程句柄(process handle)来获得进程信息。共有4中构建 handle 的方案,参考 10.9.3 节。具体的使用参考 ProcessHandle 接口中的方法。