多线程之间的临界区域问题
在多线程问题中,我们提到,当对共享资源进行同时读写的时候很可能造成多线程下的非预期的结果,因为多个线程之间的指令执行顺序是乱序的,难免balance值是110的时候读取,然后退出了程序。如果update和monitor方法只能执行一个,则会解决此问题,也就是线程同步。
1. 互斥锁同步。在前面所说的多线程问题中可以采用这种方式解决。
2. 条件同步。
生产者消费者场景下的条件同步问题
在多线程下的生产者和消费者的案例,一个线程生产数据到缓存区,一个线程从缓存区获取数据并进行消费。缓存区只有一个数据的存储位置,因此在生产者生产数据完毕后,需要等待消费者消费完毕再去生产。消费者消费完毕后,需要等待生产者再次生产才能继续消费,因此生产者线程和消费者线程之间存在着一定的协同问题。
在协同的情况下,生产者线程和消费者线程之间存在着一定的互斥同步。如果缓存区只有一个空间能够操作,那么为了避免两个线程同时操作同一资源导致的数据不一致的问题,可以采用互斥同步来解决。
互斥同步的实现
互斥同步通常采用锁的方式来实现,当某个线程想要操作公共资源的时候,需要先获取(acquire)这个资源上的锁,获取锁后,这个线程对当前资源进行独占,其他线程想要操作这个资源需要等待。此线程操作完毕后,需要将获取到的资源进行释放(release),以便于其他线程获取锁并操作公共资源。
条件同步的实现
条件同步是通过一个条件变量、三个方法(wait、signal、broadcast)实现的。条件变量定义了一个条件,wait会导致一直等待,直到条件变量返回true。signal是条件满足后,通知等待中的一个线程进行处理,broadcast是条件满足后,通知等待中的所有线程进行处理。
监视器
监视器包含一个锁、一个条件、以及相关的操作。在Java中,每个对象都存在一个与之相关的监视器。
通过对临界区的锁定,我们可以实现互斥同步。在进入临界区之前,需要先获取锁。
临界区的标记
临界区的标记可以通过synchronized进行标记。进入临界区的线程需要获取相关的锁。
1. 标记到方法上,整个方法都是临界区域。可以对实例方法进行标记,也可以标记静态方法,构造器是不允许的。关键字位置放在返回类型之前。
public class CriticalSection {
public synchronized void someMethod_1() {
// Method code goes here
}
public static synchronized void someMethod_2() {
// Method code goes here
}
}
在实例方法情况中,在调用实例同步方法的时候,首先要获取对象的锁。
// Create an object called cs_1
CriticalSection cs_1 = new CriticalSection();
// Execute the synchronized instance method. Before this method execution
// starts, the thread that is executing this statement must acquire the
// monitor lock of the cs_1 object
cs_1.someMethod_1();
在静态方法情况中,在调用静态方法之前,要先获取类锁。
// Execute the synchronized static method. Before this method execution starts,
// the thread that is executing this statement must acquire the monitor lock of
// the CriticalSection.class object
CriticalSection.someMethod_2();
2. 标记到代码块中,进入代码块之前需要获取锁。
synchronized(<objectReference>) {
// one or more statements of the critical section
}
代码块锁定的一定是一个引用类型,使用的是这个对象的监视器(不建议使用String)。代码块内部是只能单个线程执行的,代码块的外部是可以多个线程同时执行的。
public void someMethod_12() {
// some statements go here
// multiple threads can execute here at a time
synchronized(this) {
// some statements go here
// only one thread can execute here at a time
}
// some statements go here
// multiple threads can execute here at a time
}
public static void someMethod_22() {
// some statements go here: section_1
// multiple threads can execute here at a time
synchronized(CriticalSection2.class) {
// some statements go here: section_2
// only one thread can execute here at a time
}
// some statements go here: section_3
// multiple threads can execute here at a time
}
线程等待
线程等待存在于多个线程之间协同的情况。在某些条件不成立的时候,我们可以临时释放锁,让其他线程去完成条件,如果不释放,将永远卡在这里。
public class WaitMethodCall {
// Object that is used to synchronize a block
private Object objectRef = new Object();
public synchronized void someMethod_1() {
// The thread running here has already acquired the monitor lock on
// the object represented by the reference this because it is a
// synchronized and non-static method
// other statements go here
while (some condition is true) {
// It is ok to call the wait() method on this, because the
// current thread possesses monitor lock on this
this.wait();
}
// other statements go here
}
public static synchronized void someMethod_2() {
// The thread executing here has already acquired the monitor lock on
// the class object represented by the WaitMethodCall.class reference
// because it is a synchronized and static method
while (some condition is true) {
// It is ok to call the wait() method on WaitMethodCall.class
// because the current thread possesses monitor lock on
// WaitMethodCall.class object
WaitMethodCall.class.wait();
}
// other statements go here
}
public void someMethod_3() {
// other statements go here
synchronized(objectRef) {
// Current thread possesses monitor lock of objectRef
while (some condition is true) {
// It is ok to call the wait() method on objectRef because
// the current thread possesses monitor lock on objectRef
objectRef.wait();
}
}
// other statements go here
}
}
1. 调用锁的wait方法,一定要在同步方法或同步代码块的内部。
2. 可能会抛出InterruptedException,需要处理。
3. 调用的wait应该所属于当前锁的方法。
4. wait方法通常放在循环中。为什么?当线程被唤醒的时候,并不一定条件继续满足,还需要再次判断条件。
5. wait方法有其他的重载,可以设置超时时间,返回的条件不再单单是notify或者notifyAll方法,还包括超时时间到达后的返回。
线程唤醒
与线程等待相关联的就是线程唤醒,当某些条件完成后,可以唤醒其他线程继续处理。
public class WaitAndNotifyMethodCall {
private Object objectRef = new Object();
public synchronized void someMethod_1() {
while (some condition is true) {
this.wait();
}
if (some other condition is true) {
// Notify all waiting threads
this.notifyAll();
}
}
public static synchronized void someMethod_2() {
while (some condition is true) {
WaitAndNotifyMethodCall.class.wait();
}
if (some other condition is true) {
// Notify all waiting threads
WaitAndNotifyMethodCall.class.notifyAll();
}
}
public void someMethod_3() {
synchronized(objectRef) {
while (some condition is true) {
objectRef.wait();
}
if (some other condition is true) {
// Notify all waiting threads
objectRef.notifyAll();
}
}
}
}
同样的,
1. 线程唤醒也需要在同步代码块的内部。
2. 需要使用同步锁对应的notify或者notifyAll方法。