这一个Block分为四个部分,第一部分是Introduction to Threads and Concurrency ,第二部分是Interruptting and Terminating a Thread,第三部分是Keep Threads safety:the volatile variable and locks,第四部分是Beyond simple locks:the monitor。
第一部分:Introduction to Threads and Concurrency
在这一部分中,首先介绍了Processes and Threads,然后讲了如何create a thread,接着说明了一些关于thread的方法,用来control your thread and coordinate your thread.
1. Processes and Threads
关于Processes and Threads,我们首先用一张relative drawing直观的看出它们之间的关系,然后以JVM is a process来引出process与上图呼应,接着介绍了thread和process的区别,最后讲了在concurrent programming里面的四个重要概念。
① relative drawing
② JVM is a process
与上图相对应,我们可以知道:a java application runs by default in a process; work with several threads to achieve psedo parallel process / asychronous behaviour.
③ Processes and Threads
从四个方面来介绍进程和线程,运行独立,访问,资源分配
Processes | Threads |
run independently from others | a lightweight process, has its own call stack |
cannot access data in other processes | can access shares data of threads in the same process |
the resources are allocated via the operating system | has its own memory cache, reads shared data, store it and re-read the data |
④ The key concepts in concurrent programming
在并行编程中,有四个关键的概念:原子性,可见性,运行顺序和关键代码。
原子性(atomicty)指的是不能再进行分割的代码,即:when it cannot be interrupted。一旦开始运行就结束了,即:once it starts it always complete。最典型的例子就是a++的非原子性,除此之外,X = new Integer(x)(create-assign the reference)、x = y(将y的值赋值给x read-write)、x.equals(y);(比较x和y的值是否相等)均为非原子操作,他们都是多个步骤。常见的原子操作有x=3这种简单的基本数据类型的赋值。【关于原子性,在后面的第三部分volatile variable会提到,原子性也是可以使用volatile的条件】
可见性(visibility)指的是再此线程运行的时候必须看到其他线程的运行,即:when a thread must watch the actions of another thread。典型的例子是线程的终止,数值的设定。
运行顺序(the order of execution),关于运行顺序,正常程序是run in the same order everytime,并行程序是the order of execution is never guaranteed。对于这个概念,可以出相关考题,给出一段多个线程的代码,判断输出,这时候运行顺序是不确定的,输出也是不确定的。
关键代码(critical code)指的是只能由一个线程执行一次的代码部分,即:a part of code that only can be executed by a single thread at one time。【关于关键代码,在后面第三部分volatile variable中会再次出现,不同的是提出的是关键部分critical section】
2. create a thread
① The thread class
提到线程类(thread class),它的作用是executing your stuff in a thread,那stuff 在哪儿呢,your stuff is encapsulated in a run() method。
关于创建线程,我们有两种方式——使用implements Runnable和使用extends Thread,具体来说是:pass your class into a new Thread object,extend the Thread class。
Thread我们很熟悉了,是线程类,那Runnable是什么呢——Runnable is an interface that requires you to implement a run() method.
因此,对于一个线程类,我们首先要implement/extend,然后写两个函数,一个是public void run(),一个是public static void main(String[] args)。
② Implements Runnable / extends Thread
Implement Runnable 和 extend Thread除了在类名后面的内容上有所不同之外,在线程的创建上也有所不同。下面将解释一下:
public Order implements Runnable | public Order extends Thread |
Order order = new Order(); Thread t1 = new Thread(order); | Order t1 = new Order(); |
关于两种方式,更好的是Implements Runnable,因为Implements Runnable allows a subclass of Thread to be used if required,然而extends Thread——no other classes can be inherited by MyThread.
3. control your thread
① 控制线程-sleep(), yield()
关于控制线程,包括make your thread sleep 和interrupt your thread's sleep。完成这两个任务,我们需要去 make the code to execute at the appropriate time 和manage resource,一是时间二是资源。
这里我们将介绍三种方法——sleep(), yield(), interrupt()以及interrupt的相关运作机制(InterrupedException,InterruptFlag,使用Interupt的反应和使用Interupt的情况)
sleep():static method。它的作用是make the current thread sleep。使用之后它的结果是the thread will pause and will free up CPU for other threads。
yield():static method。它的结果是the executing thread is suspended and the CPU is given to other runnable thread。换一种说法是the executing thread is returned to the ready queue of the processor and wait for its next turn。
② interrupt()
在程序运行中会发生线程的block(a thread if prevented from doing anything),原因可能是waiting for a monitor lock 或者时suspended by call to wait()[become runnable on a notify/ notifyAll]。【Block发生的原因】
那么当我们遇到block的时候如何处理呢——interrupt()!【使用Interupt的情况】
interrupt():non-static method。当使用它的时候需要用t1.interrupt()/Thread.current Thread().interrupt(),那么在使用之后会发生什么呢?【interrupt()】
分为两种情况,第一种是当线程sleep的时候,第二种是线程没有sleep的时候。sleep时,进行interrupt,首先会将interrupt flag中断标记设为true,然后会throw InterruptedException,同时会clear the interrupt flag清除中断标记;没有sleep时,进行interrupt,只会将interrupt flag中断标记设为true。【使用Interupt的反应】
什么是InterruptedException,什么是interrupt flag中断标记呢?
InterruptedException,会在the thread is interrupted while sleeping的时候发生,作用是enable you to deal with interrupt elegantly. 在try...catch(InterruptedException e){e.printStackTrace()}语句中进行使用。【InterrupedException】
interrupt flag,存在于every Thread object,为了标记中断会change this flag to true。如果值为true时,需要finish the method immediatly;如果值为false,continual as normal。当然这也是建立在是否为sleep state的基础上。【interrupt flag】
4. coordinate your thread
对于协调线程,我们需要用到join()方法:non-static method。它的作用时pause util the other thread has terminated(use on any thread to wait for it to die),跟sleep一样,需要用到InterruptException
第二部分:Interrupting and Terminating threads
1. Interrupting threads
关于中断线程,我们分为三个部分进行讲解——中断的概念,中断机制和判断线程是否中断的方法。
① Interrupt
Interrupt(中断)是an indication to a thread that it should stop what it is doing and do something else.(not stop the thread)。那我们如何使其停止呢?——The programmer decides how a thread responds to a thread。也就是说,首先需要进行中断(A thread sends an Interrupt by invoking interrupt() on the Thread object to be interrupted),然后再根据programmer指示去做,这个过程我们需要Interrupt mechenism的支持。【interrupt的概念、使用】
② Interrupt mechanism
Interrupt mechanism的实现离不开中断标记(Interrupt flag/interrupt status)。对于中断标记的设置,我们需要用到Interrupt()函数,对于中断标记的检查,我们可以用两个方法——interrupted()和isInterrupted()
③ Interrupt methods
下面我们将从类型、作用和特点三个方面来讲述两者的区别
Thread.interrupted() | t1. isInterrupted() |
static | non-static |
check the current thread | check the Thread object that it is called on |
clear the status of the current thread | / |
2. Terminating threads
上一章节,我们提到了Exception,关于为什么使用Exception,我们有两种解释——deal with an unusual situation, elegantly finish whatever we are doing.但是终止结束线程,首先要保证线程是alive的,然后再进行终止方法的选择。
① IsAlive()
线程alive指的是when it has been started and is not dead yet。判断线程alive需要用到isAlive()方法,thread is alive——return true。
② 3 ways to terminate
关于终止线程,我们有三种方式,分别是——finish the thread naturally, deamon threads 和interrupt the thread。
finish the thread naturally有两种情况——doing nothing(start-let it finish)和use a shared Boolean(periodically check to see if pleaseFinish is set to true, true——finish thread)。我们重点注意的是第二种使用Boolean的方式,示例代码如下:
public volatile boolean pleaseFinish = false;
public void finishThreadPlease(){
pleaseFinish=true;
}
我们需要注意的是,这个方法使用的有效是存在条件的,即thread需要alive——runnable。non-runnable的时候有以下几种情况——sleep(), wait(), blocking on IO。如果我们想要迅速得到这个线程,可以使用interrupt方法。
daemon threads也存在两种情况,但是与上述不同的是,它是通过方法setDeamon来设置的——setDeamon(true)和setDeamon(flase)。setDeamon(true)是the thread terminates when the parent thread terminates,setDeamon(false)是the thread continues to run after the parent thread has finished.需要注意的是,需要在线程开始之前进行设置,示例代码如下:
public void run(){
WorkerThread t1 = new WorkerThread();
t1.setDeamon(true);
t1.start();
}
第三种方式也是我们熟悉的Interrupt,在此前面的内容不再赘述,就interrupt之后的反应再进行详细讨论。首先,我们需要知道interrupt是an indication并非强制停止,因此it will not automatically stop a thread unless you programme to do so。然后,在线程中断之后,我们需要做出反应,这时需要用到InterruptedException来抛出异常。但是呢,这个异常的抛出只是在sleep()/wait()方法作用之后才会发生,接下来我们就需要用到isInterrupted()和interruted()方法来routinely check for interrupts at strategic points where you can safely stop and cleanup.最后呢,我们再来讨论一下exception的情况,exception被抛出时说明线程是处在sleep()/wait()时,我们知道在exception被抛出之前的时候,sleep会将interrupt status清除——clear the interrupt status。为了重新保存中断状态,我们需要进行在exception的catch语句中重新进行一次中断。示例代码如下:
//InterruptedExcption抛出异常
public void run(){
//...
try{
Thead.sleep(5000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
//restore the interrupt status
public void run(){
//..
try{
Thread.sleep(5000);
}catch(InterruptedException e){
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
第三部分:Keep Threads safety:the volatile variable and locks
当不同的线程访问同一个变量或者是同一个方法的时候,会出现冲突,为了解决变量访问的冲突,我们提出了volatile variable,为了解决方法访问的冲突,我们提出了lock。
1. Keeping your code safe the volatile keyword
① Thread safe code
关于线程安全有两个要点,首先是多线程操作共享数据,然后是不同时进行操作,即manipulate shared data structure in a manner that guarantees safe execution by multiple threads at the same time
② volatile
volatile关键字很重要,没有了volatile关键字,代码就不能正常工作。那volatile有什么用呢?volatile是用来表示变量值会被不同线程修改,即indicate that a variable's value will be modified by different threads,因此就可以实现变量只会被单线程访问,即access to the variable is only given to one thread at a time。但是并不是所有的变量都可以被volatile进行修饰,当加载、读取、写入等工作在变量上都是atomic的时候,才可以使用。【volatile的概念和特点】
那我们什么时候不能够或者不需要使用volatile呢?有三种情况,变量为final,变量只会被单一线程访问 by single thread,complex operation。示例代码如下:【volatile不需要使用的时候】
public class StoppableTask extends Thread{
private volatile boolean pleaseStop;
public void run(){
while(!pleaseStop){// }
}
public void tellMeToStop(){
pleaseStop = true;
}
}
③ Common traps with volatile常见陷阱
关于volatile,我们知道使用条件具有原子性,访问具有单一性,因此在使用条件和访问上会有以下常见陷阱:
如果将数组arr[]声明为volatile,数组的reference(arr)是volatile的,但是individual field accesses(arr[0],arr[1]...)不是thread-safe。
unary operation(++, --) 不是atomic,不是thread-safe>
在数据访问过程中,我们需要进行变量缓存。但是如果变量被修改了,其他线程的缓存就会过时。因此,变量的值不会被本地缓存,所有的读取和写入都之间到main memory中。即 The value of variables will never be cached thread-locally, all reads and writes will go straight to "main memory"。
2. Beyond volatile: using locks
除了变量需要同一时刻被单一线程访问以外,还有代码块也需要如此。但是volatile只能对atomic operation进行操作(本章节只涉及变量——基本变量类型),不能对complex operation进行操作,所以我们需要引入lock。下面将介绍critical section和lock的相关问题,其中对于lock的介绍,我们分为
① Critical sections
我们之前在第一部分提到过critical code的概念,即a part of code that can be executed by a single thread at a time。critical section将引入data和多个分别的并发的线程来进行另一种类似的表述,即 the code segments within a program that access the same data from within seperate, concurrent threads. 这部分critical section需要用synchronized关键词进行修饰,也称作synchronized section。对于synchronized section,只能在给定时刻被单一线程访问,即can only be accessed by a single thread at any given time.【critical section的概念,关键词和特点】
② Locks
关于锁的使用,锁是作用在一个特定的代码部分上面的,即 A lock applies to a particular section of code。当我们使用锁之后,If the code is locked, no other thread can execute it. If the code is unlocked, any thread can take the lock and execute it。【锁的使用和效果】
关于锁的分类,可以分为内部锁Intrinsic locks 和外部锁Extrinsic locks。Intrinsic locks 是 每个对象都可以通过使用sychronized关键词来起到锁的作用。every object can function as a lock that is triggered using the keyword synchronized.
其中,我们需要注意到锁的线程唯一性:
只有一个线程可以在同一时刻执行这个方法,当一个线程拥有lock时,其他线程不能再获取lock了,必须等到其释放。Only one thread can execute the method at the same time. When a thread has a lock, no other thread can acquire it. It must wait for the first thread to release the lock.
如果两个方法被sychronized关键词修饰,只有一个可以执行,因为相同锁会被一个对象中的所有方法使用。Two methods with the synchronized keyword, only one method of the two will be executed at the same time. Because the same lock is used for all methods in an object.
③ Intrinsic lock 内部锁
内部锁是每个对象通过使用sychronized关键词来起到锁的作用,那么我们如何来利用到sychronized关键词呢?我们有两种使用方式
Synchronized methods
protected synchronized int getNextAvailableItem() {
… return items;
}
protected int getNextAvailableItem() {
synchronized (this){
… return items;
}
}
如果方法为静态的话,那么使用的锁是Class对象——If the method is static, the used lock is the Class object
Explicit use of the intrinsic lock(Synchronized statements)
public class Example {
private int value = 0;
public int getNextValue() {
synchronized (this) {
return value++;
}
}
}
与synchronized methods不同, synchronized statements必须指定提供内部锁的对象——must specify the object that provides the intrinsic lock。通常情况下,关键部分是方法,也可以标记更小段为sychronized——critical sections in Java programs are methods. You can mark smaller code segments as synchronized. 对于大多数的java程序来说,最好再方法水平上使用synchronized。
④ Scope of a lock 锁的范围/作用域
以上,我们讨论了lock的使用和效果——作用域特定的代码块,locked之后无法被执行;然后讨论了lock的分类,重点介绍了intrinsic lock——定义、两种不同的使用方式以及作用水平。现在我们将讨论锁的范围。
lock的范围是在锁使用和释放之间的时间——The time between when the lock is taken and when the lock is released。那这个时间段是由什么来决定的呢?它是由代码段所决定的——Lock scopes can be determined by segments of code (a method or just a part of code)。再次注意,所作用的对象是对象不是方法——locks apply to objects not methods。并且,锁不是方法而是保护方法的东西—— A lock isn’t a method, it’s something that protects a method。
⑤ Full/Partial synchronization 完全同步和部分同步
完全同步指的是每个方法都同步的类(无公共实例变量)保证了局部顺序行为—— A class in which every method is synchronised (that has no public instance variables) guarantees locally sequential behaviour. 因此,他们在一个时刻制作一件事情——They only do one thing at a time:ready (idle - not having the lock) / active (processing a method) / waiting (for a reply)
⑥ 其他
讨论完完全同步,我们还有部分同步未讨论,现在我们要从方法的调用、方法的执行、方法的本身来进行详细的讨论:
方法的调用:synchronized方法可调用unsynchronized方法——只有synchronized方法会获取lock,unsynchronized方法不会获取lock,所以可在synchronized方法中调用unsynchronized方法。synchronized方法可以调用另一个synchronized方法——同一object的synchronized方法share the same lock,已经持有lock的线程可继续执行其他synchronized方法,不需重新获取lock。
方法的执行:标记为synchronized的方法或代码块将完整地执行——除非被wait方法explicitly suspended,否则在synchronized块完成之前,其他线程将被blocked。未被标记为synchronized的方法可以立即执行——即使对象的另一个方法正在执行,甚至是一个同步方法。对于非同步方法,没有要获取的锁,所以它们可以并行执行。
方法的对象:每个object只有一个lock——每个object都有一个关联的lock,用于实现synchronization。对于static方法,使用的是该class对象的lock。
// 一个non-static方法可以使用代码块锁定静态数据—— 使用类对象作为同步锁
synchronized (getClass()) {...}
// 只能在对象上进行同步操作,而不能在基本类型变量上进行同步。synchronized (temp) { temp = 10; }
方法的实现:lock是一个counter——每当一个thread进入一个synchronized方法或块时,就会增加。如果counter不为0,表示有thread已经持有该lock,当前thread将被阻塞,直到计数器为0。当线程退出synchronized方法或块时,counter会递减。
方法的继承:synchronized关键字不会自动继承到子类方法中。当重写一个方法时,如果父类的方法被synchronized修饰,子类中覆盖该方法时需显式地加上synchronized,否则子类方法将不会同步
Synchronized | volatile |
method or scope declared synchronized | primitive variable declared volatile |
A synchronized method can protect more complex code | Access to a volatile variable never has the potential to block – Volatile only protects atomic operations – not suitable for complex operations, a ++ |
第四部分:Beyond simple locks:the monitor
1. Monitor
在保证线程安全中,我们引入了volatile,作用于变量,保证变量访问的安全性,然后引入了锁,作用于代码块,保证方法(代码块)使用的安全性。但是我们只是简单的让方法与方法之间互斥,并没有使其能够达到合作。因此提出了monitor。
① From intrinsic locks to monitors
关于intrinsic lock和monitor,两者之间在使用和作用方面不同。
intrinsic lock | monitor |
support Mutual Exclusion through the use of the keyword synchronized | support Cooperation through the wait() & notify() methods |
Only allow one thread to execute a part of code at a given time | Enable threads to work together (Wait and Notify monitors or Signal and Continue monitors) |
对于内部锁,实现是通过在方法/代码段上使用synchronized关键词,并通过对象调用方法或者在synchronized statement上指定作用对象来实现的。那monitor是如何实现的呢?Every object can be a monitor 。我们还需要三个部分来实现monitor
synchronized | creates the lock to protect the critical section of the code |
wait() on an object | pauses a thread and puts it in a wait set (the set of threads waiting for the lock to become free) |
notify() on that object | reawakens a thread from the wait set |
对于Monitor来说,只是 another lock?在原有的基础上,Using the synchronized keyword ensure mutual exclusion—— Only one thread can execute the method at a given time,还添加了功能, allows cooperation between threads—— Allows threads to pause their execution and notify other threads of events.
② Entry set, wait set, owners
对于这张图,我们可以看出分为左中右三个部分,左边是entry set(enter:进入lock region的线程——需要被放入相关monitor的entry set),中间是lock region/the owner(acquire:执行lock region的线程——没有其他的线程在entry set中等待,当前线程成功地获取了锁),右边是wait set( release and exit : the thread finishes executing the lock region)
wait set是我们的重点,进入和出去两个途径进行讲述——release和acquire。如何release(进入wait set)呢?A thread that currently owns the lock can suspend itself inside the lock by executing a wait() command,在执行wait()后——releases the lock and enters a wait set, the thread will stay suspended in the wait set until another thread executes a notify() command inside the lock。为了从wait set中出去,我们需要用到notify()——When a thread executes a notify, it continues to own the lock until it releases the lock of its own accord, either by executing a wait or by completing the lock region; After the notifying thread has released the lock, the waiting thread will be resurrected and will reacquire the lock.
Notify 说明哪一组可以竞争—— wait or entry set:
① If the (former) lock owner did not execute a notify before it released the lock:then only the threads in the entry set will compete to acquire the lock.
If the (former) lock owner did execute a notify:then the entry set threads will have to compete with one or more threads from the wait set.
② thread wins the competition
from the entry set , it becomes the new owner of the lock.
from the wait set wins the competition, it exits the wait set and reacquires the lock.
A thread can only execute a wait command if it currently owns the lock – i.e., it is currently inside the synchronized block.
Notify有两种—— “notify”和“notify all”
① notify(): selects one thread arbitrarily from the wait set and marks it for eventual resurrection
② notifyAll():marks all threads currently in the wait set for eventual resurrection
2. Deadlocks
① 定义:Two or more threads waiting for two or more locks to be freed, and the circumstances in the program is such that the locks will never be freed
② solutions
• Prevention:Design code so deadlock is impossible
– Avoid mutual exclusion
– Allow pre-emption
– Don’t allow a thread to hold multiple locks
• Avoidance:Steer around deadlock with smart scheduling
• Detection and Recovery:Check for deadlock periodically; Recover by killing threads and restarting Deadlock solutions