关于java多线程2

来源:互联网 发布:模块数据出现异常 编辑:程序博客网 时间:2024/05/29 04:50

二、关于Java Concurrency的理论知识

1.Java Concurrency / Multithreading Tutorial

多任务->多线程

However, mulithreading is even more challenging than multitasking. The threads are executing within the same program and are hence reading and writing the same memory simultanously. 读写同一块内存

而多任务只是单纯切换CPU时间,并不读写相同的内存块。

多线程:
这里写图片描述

需要学习control how threads access shared resources like memory, files, databases etc. That is one of the topics this Java concurrency tutorial addresses.

New functional programming parallelism has been introduced with the Fork and Join framework in Java 7, and the collection streams API in Java 8.

2.Multithreading Benefits

Some of these benefits are:

1.Better resource utilization.
2.Simpler program design in some situations.
3.More responsive programs.

Better resource utilization

Imagine an application that reads and processes files from the local file system.

  5 seconds reading file A  2 seconds processing file A  5 seconds reading file B  2 seconds processing file B----------------------- 14 seconds total

When reading the file from disk most of the CPU time is spent waiting for the disk to read the data. The CPU is pretty much idle during that time. It could be doing something else. By changing the order of the operations, the CPU could be better utilized.

  5 seconds reading file A  5 seconds reading file B + 2 seconds processing file A  2 seconds processing file B----------------------- 12 seconds total

In general, the CPU can be doing other things while waiting for IO. It doesn’t have to be disk IO. It can be network IO as well, or input from a user at the machine. Network and disk IO is often a lot slower than CPU’s and memory IO.

对于单CPU的并发,本质应该是提高了CPU的利用率。

Simpler Program Design

如果硬编码上述步骤,需要复杂步骤,对于更多流程的东西,硬编码肯定不行。通过提供多线程支持,能够自动在wait时让出CPU时间,进行切换。

More responsive programs

例子:
1.server持续监听port。
2.UI线程在接收点击事件后仍能响应其他操作。

3.Multithreading Costs

More complex design

Code executed by multiple threads accessing shared data need special attention.

Context Switching Overhead

When a CPU switches from executing one thread to executing another, the CPU needs to save the local data, program pointer etc. of the current thread, and load the local data, program pointer etc. of the next thread to execute.

仔细了解上述步骤,是CPU执行了线程1,在要切换时存入线程1的结果,然后load进线程二的结果,接着执行。所以,线程之间肯定要解决线程安全和同步的问题,因为切换时间不定,必须有一个强制的操作来使程序执行正确的期望的结果。

线程安全的定义应是:无论线程怎么切换,什么时间切换,都不会影响我们的执行结果。

Increased Resource Consumption

4.Concurrency Models

不同的并发框架有不同的实现形式。了解常见的并发模型。

Concurrency Models and Distributed System Similarities

并发模型跟分布式系统有点像。通常,前者是一个电脑中线程之间通信,后者是多个电脑中进行之间的通信。

第一种模型:

1.Parallel Workers
这里写图片描述

In the parallel worker concurrency model a delegator distributes the incoming jobs to different workers. Each worker completes the full job. The workers work in parallel, running in different threads, and possibly on different CPUs.

Many of the concurrency utilities in the java.util.concurrent Java package are designed for use with this model.

它的好处是易于理解。

缺点:

1.Shared State Can Get Complex

这里写图片描述

The threads need to access the shared data in a way that makes sure that changes by one thread are visible to the others (pushed to main memory and not just stuck in the CPU cache of the CPU executing the thread). Threads need to avoid race conditions, deadlock and many other shared state concurrency problems.

Many concurrent data structures are blocking, meaning one or a limited set of threads can access them at any given time.High contention will essentially lead to a degree of serialization of execution of the part of the code that access the shared data structures.

虽然有一些Non-blocking Algorithms能够减少竞争和提高性能,但是其很难实现。

持久化数据结构是另一种选择。A persistent data structure always preserves the previous version of itself when modified.

Thus, if multiple threads point to the same persistent data structure and one thread modifies it, the modifying thread gets a reference to the new structure. All other threads keep a reference to the old structure which is still unchanged and thus consistent.

翻译就是,如果多个线程都占有该数据,一个线程修改了该数据,那么该修改线程能得到最新值,其他线程还是原来未改变的值。

但是这种方法不是很有用。举例:

For instance, a persistent list will add all new elements to the head of the list, and return a reference to the newly added element (which then point to the rest of the list). All other threads still keep a reference to the previously first element in the list, and to these threads the list appear unchanged. They cannot see the newly added element. 肯定看不到新的改变啊。

持久化方式只有修改线程能够看到改变,其他同时的线程还是看不到。

2.Stateless Workers

Shared state can be modified by other threads in the system. Therefore workers must re-read the state every time it needs it, to make sure it is working on the latest copy. This is true no matter whether the shared state is kept in memory or in an external database. A worker that does not keep state internally (but re-reads it every time it is needed) is called stateless .

Re-reading data every time you need it can get slow. Especially if the state is stored in an external database.

3.Job Ordering is Nondeterministic

第二种模型:
Assembly Line

这里写图片描述

Each worker is running in its own thread, and shares no state with other workers. This is also sometimes referred to as a shared nothing concurrency model.

这里写图片描述

Systems using the assembly line concurrency model are usually designed to use non-blocking IO.

这里写图片描述

第二种模型别名:
Reactive, Event Driven Systems

Systems using an assembly line concurrency model are also sometimes called reactive systems, or event driven systems (事件驱动系统). The system’s workers react to events occurring in the system, either received from the outside world or emitted by other workers. Examples of events could be an incoming HTTP request, or that a certain file finished loading into memory etc.

Actors vs. Channels

Actors and channels are two similar examples of assembly line (or reactive / event driven) models.是两种ssembly line的例子。

In the actor model each worker is called an actor. Actors can send messages directly to each other. Messages are sent and processed asynchronously. Actors can be used to implement one or more job processing assembly lines, as described earlier. Here is a diagram illustrating the actor model:
这里写图片描述

In the channel model, workers do not communicate directly with each other. Instead they publish their messages (events) on different channels. Other workers can then listen for messages on these channels without the sender knowing who is listening. Here is a diagram illustrating the channel model:
这里写图片描述

Assembly Line Advantages

1.No Shared State

You implement a worker as if it was the only thread performing that work - essentially a singlethreaded implementation.可以按照是单线程执行来进行编码,不必考虑多线程的问题。

2.Stateful Workers

Since workers know that no other threads modify their data, the workers can be stateful.By stateful I mean that they can keep the data they need to operate in memory, only writing changes back the eventual external storage systems. A stateful worker can therefore often be faster than a stateless worker.

3.Better Hardware Conformity

Singlethreaded code has the advantage that it often conforms better with how the underlying hardware works. First of all, 1.you can usually create more optimized data structures and algorithms when you can assume the code is executed in single threaded mode.

2.Second, singlethreaded stateful workers can cache data in memory as mentioned above. When data is cached in memory there is also a higher probability that this data is also cached in the CPU cache of the CPU executing the thread. This makes accessing cached data even faster.

4.Job Ordering is Possible

Assembly Line Disadvantages

主要有两个

The main disadvantage of the assembly line concurrency model is that 1.the execution of a job is often spread out over multiple workers, and thus over multiple classes in your project. Thus it becomes harder to see exactly what code is being executed for a given job.

It may also be 2.harder to write the code. Worker code is sometimes written as callback handlers. Having code with many nested callback handlers may result in what some developer call callback hell. Callback hell simply means that it gets hard to track what the code is really doing across all the callbacks, as well as making sure that each callback has access to the data it needs.

第三种模型:
Functional Parallelism

Which Concurrency Model is Best?

5.Same-threading

放弃这一章

6.Concurrency vs. Parallelism

The terms concurrency and parallelism are often used in relation to multithreaded programs.究竟它们是什么区别呢?

并发
这里写图片描述

并行
这里写图片描述

7.Creating and Starting Java Threads

基础,不看

8.Race Conditions and Critical Sections

A race condition is a special condition that may occur inside a critical section. A critical section is a section of code that is executed by multiple threads and where the sequence of execution for the threads makes a difference in the result of the concurrent execution of the critical section.线程执行顺序不一样导致结果不一样,所有才有加锁,同步等措施啊。

Critical Sections

Running more than one thread inside the same application does not by itself cause problems. The problems arise when multiple threads access the same resources.突然想到一个UI程序中必然会有多线程,但是如果没有很多线程用相同的资源,我们确实可以不用担心多线程问题。

In fact, problems only arise if one or more of the threads write to these resources. It is safe to let multiple threads read the same resources, as long as the resources do not change.
下面程序会在多线程中出错:

public class Counter {     protected long count = 0;     public void add(long value){         this.count = this.count + value;     }  }

Race Conditions in Critical Sections

More formally, the situation 1.where two threads compete for the same resource, 2.where the sequence in which the resource is accessed is significant, is called race conditions. A code section that leads to race conditions is called a critical section.

To prevent race conditions from occurring you must make sure that the critical section is executed as an atomic instruction.必须保证关键区域被当做一个原子指令来执行。

That means that once a single thread is executing it, no other threads can execute it until the first thread has left the critical section.敏感区域要特别对待。

有很多线程同步方法可以解决Race conditions:

1.using a synchronized block of Java code
2.locks
3.atomic variables like java.util.concurrent.atomic.AtomicInteger

Critical Section Throughput

for larger critical sections it may be beneficial to break the critical section into smaller critical sections, to allow multiple threads to execute each a smaller critical section. This may decrease contention on the shared resource, and thus increase throughput of the total critical section.

比如:

public class TwoSums {    private int sum1 = 0;    private int sum2 = 0;    public void add(int val1, int val2){        synchronized(this){            this.sum1 += val1;               this.sum2 += val2;        }    }}

可以改写成:

public class TwoSums {    private int sum1 = 0;    private int sum2 = 0;    //巧妙地加入lock    private Integer sum1Lock = new Integer(1);    private Integer sum2Lock = new Integer(2);    public void add(int val1, int val2){        synchronized(this.sum1Lock){            this.sum1 += val1;           }        synchronized(this.sum2Lock){            this.sum2 += val2;        }    }}

9.Thread Safety and Shared Resources

Therefore it is important to know what resources Java threads share when executing.

Local Variables

Local variables are stored in each thread’s own stack. That means that local variables are never shared between threads. That also means that all local primitive variables are thread safe.线程私有,因此是线程安全的。例如:

public void someMethod(){  long threadSafeInt = 0;  threadSafeInt++;}

Local Object References

Local references to objects are a bit different. The reference itself is not shared. The object referenced however, is not stored in each threads’s local stack. All objects are stored in the shared heap.

注意就是,指向对象的reference本身是线程私有的,但是指向的对象是在heap上分配的嘛,因此是共享的。正确的像一句废话。

多线程条件下,每个线程内部还是顺序执行的呀,这个要注意!

If an object created locally never escapes the method it was created in, it is thread safe. In fact you can also pass it on to other methods and objects as long as none of these methods or objects make the passed object available to other threads.
比如:

public void someMethod(){  LocalObject localObject = new LocalObject();  localObject.callMethod();  method2(localObject);}public void method2(LocalObject localObject){  localObject.setValue("value");}

多线程中每一个线程进入someMethod都会new一个新对象。

The LocalObject instance in this example is not returned from the method, nor is it passed to any other objects that are accessible from outside the someMethod() method. Each thread executing the someMethod() method will create its own LocalObject instance and assign it to the localObject reference. Therefore the use of the LocalObject here is thread safe.

The only exception is(错误情况), if one of the methods called with the LocalObject as parameter, stores the LocalObject instance in a way that allows access to it from other threads. 这种情况下就会出错。

Object Member Variables

成员变量不是线程安全的。

Object member variables (fields) are stored on the heap along with the object(都是在heap上的). Therefore, if two threads call a method on the same object instance and this method updates object member variables, the method is not thread safe. Here is an example of a method that is not thread safe:

public class NotThreadSafe{    StringBuilder builder = new StringBuilder();    public add(String text){        this.builder.append(text);    }}

If two threads call the add() method simultaneously on the same NotThreadSafe instance(产生race condition) then it leads to race conditions. For instance:

NotThreadSafe sharedInstance = new NotThreadSafe();new Thread(new MyRunnable(sharedInstance)).start();new Thread(new MyRunnable(sharedInstance)).start();public class MyRunnable implements Runnable{  NotThreadSafe instance = null;  public MyRunnable(NotThreadSafe instance){    this.instance = instance;  }  public void run(){    this.instance.add("some text");  }}

However, if two threads call the add() method simultaneously on different instances(不产生race condition) then it does not lead to race condition. Here is the example from before, but slightly modified:

new Thread(new MyRunnable(new NotThreadSafe())).start();new Thread(new MyRunnable(new NotThreadSafe())).start();

上面对比告诉我们重要经验:
even if an object is not thread safe it can still be used in a way that doesn’t lead to race condition.没有竞争相同资源或者其他情况都有可能啊。

The Thread Control Escape Rule

当判断你的程序access某一个resource是否是线程安全时的一条准则:

If a resource is created, used and disposed withinthe control of the same thread,and never escapes the control of this thread,the use of that resource is thread safe.

这里dispose的意思是losing or null’ing the reference to the object.

但是请注意:

Even if the use of an object is thread safe, if that object points to a shared resource like a file or database, your application as a whole may not be thread safe. 创建的对象又引用了相同的资源

For instance, if thread 1 and thread 2 each create their own database connections, connection 1 and connection 2, the use of each connection itself is thread safe. But the use of the database the connections point to may not be thread safe. For example, if both threads execute code like this:

check if record X existsif not, insert record X

很有必要分清楚

Therefore it is important to distinguish between whether an object controlled by a thread is the resource, or if it merely references the resource (like a database connection does).

10.Thread Safety and Immutability

Race conditions occur only if 1.multiple threads are accessing the same resource, and 2.one or more of the threads write to the resource. If multiple threads read the same resource race conditions do not occur.如果只是读,那么不会发生race condition。
比如下面的类没有setter方法,没法改变value值,那么就可以并发读,而不用加限制。

public class ImmutableValue{  private int value = 0;  public ImmutableValue(int value){    this.value = value;  }  public int getValue(){    return this.value;  }}

要改变值的线程安全方式:

public class ImmutableValue{  private int value = 0;  public ImmutableValue(int value){    this.value = value;  }  public int getValue(){    return this.value;  }          public ImmutableValue add(int valueToAdd){      //创建一个新对象,但还是没有setter      return new ImmutableValue(this.value + valueToAdd);      }}

The Reference is not Thread Safe!

It is important to remember, that even if an object is immutable and thereby thread safe, the reference to this object may not be thread safe.下面是例子:

public class Calculator{  private ImmutableValue currentValue = null;  public ImmutableValue getValue(){    return currentValue;  }  public void setValue(ImmutableValue newValue){    this.currentValue = newValue;  }  public void add(int newValue){    this.currentValue = this.currentValue.add(newValue);  }}

这里相表达的意思是:
The ImmutableValue class is thread safe, but the use of it is not.

To make the Calculator class thread safe you could have declared the getValue(), setValue(), and add() methods synchronized. That would have done the trick.

11.Java Memory Model

The Internal Java Memory Model

这里写图片描述

All local variables of primitive types ( boolean, byte, short, char, int, long, float, double) are fully stored on the thread stack and are thus not visible to other threads. One thread may pass a copy of a pritimive variable to another thread, but it cannot share the primitive local variable itself.

The heap contains all objects created in your Java application, regardless of what thread created the object.所有的对象都存在heap上。

重要:

A local variable may be of a primitive type, in which case it is totally kept on the thread stack.基础类型变量在stack上。

A local variable may also be a reference to an object. In that case the reference (the local variable) is stored on the thread stack, but the object itself if stored on the heap.

An object may contain methods and these methods may contain local variables. These local variables are also stored on the thread stack, even if the object the method belongs to is stored on the heap. 方法中的变量还是在stack上,尽管方法所属对象在heap上。

An object’s member variables are stored on the heap along with the object itself. That is true both when the member variable is of a primitive type, and if it is a reference to an object.成员变量不管是什么类型,都存在heap上。

Static class variables are also stored on the heap along with the class definition.静态类变量也在heap上。

Objects on the heap can be accessed by all threads that have a reference to the object. When a thread has access to an object, it can also get access to that object’s member variables. If two threads call a method on the same object at the same time, they will both have access to the object’s member variables, but each thread will have its own copy of the local variables.
注意,有copy的是local variables,并不是说每个thread都要copy对象上的member variables。

如下图:
这里写图片描述

Hardware Memory Architecture

这里写图片描述

Bridging The Gap Between The Java Memory Model And The Hardware Memory Architecture

他们之间的对应关系:
这里写图片描述

When objects and variables can be stored in various different memory areas in the computer, certain problems may occur. The two main problems are:

1.Visibility of thread updates (writes) to shared variables.可见性
2.Race conditions when reading, checking and writing shared variables.

Visibility of Shared Objects

If two or more threads are sharing an object, without the proper use of either volatile declarations or synchronization, updates to the shared object made by one thread may not be visible to other threads.如果不加这些,可能导致修改不可见。

在计算机硬件架构中,并不是每次在cache memory或者register中更新都会同步到主存中。所以才会出现不可见的情况嘛。

不可见的情况:

这里写图片描述

To solve this problem you can use Java’s volatile keyword. The volatile keyword can make sure that a given variable is read directly from main memory, and always written back to main memory when updated.直接从主存中读取,并且每次修改都会更新至主存。这样,其他线程就能看见啦。

Race Conditions

If two or more threads share an object, and more than one thread updates variables in that shared object, race conditions may occur.

比如下图:
这里写图片描述

To solve this problem you can use a Java synchronized block. A synchronized block guarantees that only one thread can enter a given critical section of the code at any given time. Synchronized blocks also guarantee that all variables accessed inside the synchronized block will be read in from main memory, and when the thread exits the synchronized block, all updated variables will be flushed back to main memory again, regardless of whether the variable is declared volatile or not.有了同步操作,会从主存中读,写也会同步到主存中。

12.Java Synchronized Blocks

The synchronized mechanism was Java’s first mechanism for synchronizing access to objects shared by multiple threads. The synchronized mechanism isn’t very advanced though. That is why Java 5 got a whole set of concurrency utility classes to help developers implement more fine grained concurrency control than what you get with synchronized.不推荐用synchronized。

13.Java Volatile Keyword

The Java volatile keyword is used to mark a Java variable as “being stored in main memory”. More precisely that means, that every read of a volatile variable will be read from the computer’s main memory, and not from the CPU cache, and that every write to a volatile variable will be written to main memory, and not just to the CPU cache.

The Java volatile Happens-Before Guarantee

volatile不光保证读写都只会跟主存有关。此外还保证:

1.If Thread A writes to a volatile variable and Thread B subsequently reads the same volatile variable, then all variables visible to Thread A before writing the volatile variable, will also be visible to Thread B after it has read the volatile variable.注意之前和之后。

2.不会被JVM指令重排

关于第一点特性:

When a thread writes to a volatile variable, then not just the volatile variable itself is written to main memory. Also all other variables changed by the thread before writing to the volatile variable are also flushed to main memory. When a thread reads a volatile variable it will also read all other variables from main memory which were flushed to main memory together with the volatile variable.

举例:

Thread A:    sharedObject.nonVolatile = 123;    sharedObject.counter     = sharedObject.counter + 1;Thread B:    int counter     = sharedObject.counter;    int nonVolatile = sharedObject.nonVolatile;

Since Thread A writes the non-volatile variable sharedObject.nonVolatile before writing to the volatile sharedObject.counter, then both sharedObject.nonVolatile and sharedObject.counter are written to main memory when Thread A writes to sharedObject.counter (the volatile variable).两个值都会更新到主存中。

Since Thread B starts by reading the volatile sharedObject.counter, then both the sharedObject.counter and sharedObject.nonVolatile are read from main memory into the CPU cache used by Thread B. By the time Thread B reads sharedObject.nonVolatile it will see the value written by Thread A. 读也会从主存中读。

可以利用特性一来做一些简化。
Instead of declaring each and every variable volatile, only one or a few need be declared volatile. Here is an example of a simple Exchanger class written after that principle:

public class Exchanger {    private Object   object       = null;    private volatile hasNewObject = false;    public void put(Object newObject) {        while(hasNewObject) {            //wait - do not overwrite existing new object        }        object = newObject;        hasNewObject = true; //volatile write    }    public Object take(){        while(!hasNewObject){ //volatile read            //wait - don't take old object (or null)        }        Object obj = object;        hasNewObject = false; //volatile write        return obj;    }}

因为在while中读取了volatile变量,那么接下来读取的变量值都为最新的。

This Exchanger can work just fine using a volatile variable (without the use of synchronized blocks), as long as only Thread A calls put() and only Thread B calls take().只要A只调用put,B只take。

volatile is Not Always Enough

volatile在下面的情况能够成功:

In fact, multiple threads could even be writing to a shared volatile variable, and still have the correct value stored in main memory, if the new value written to the variable does not depend on its previous value. In other words, if a thread writing a value to the shared volatile variable does not first need to read its value to figure out its next value.

当前计算结果不需要依赖先前值。

As soon as a thread needs to first read the value of a volatile variable, and based on that value generate a new value for the shared volatile variable, a volatile variable is no longer enough to guarantee correct visibility.

只要依赖先前值,那么volatile将不能保证正确的可见性。

read和write之间的间隙中就产生了race condition。

When is volatile Enough?

正如上面的情况,多个线程都会读写该volatile变量并不会得到正确结果。

正确的情况是:

In case only one thread reads and writes the value of a volatile variable and other threads only read the variable, then the reading threads are guaranteed to see the latest value written to the volatile variable. Without making the variable volatile, this would not be guaranteed.

只有一个线程读和写,另外的线程都只读

Performance Considerations of volatile

因为每次都要读写主存,并且volatile避免了指令重排,性能有所下降。

you should only use volatile variables when you really need to enforce visibility of variables.

14.Java ThreadLocal

The ThreadLocal class in Java enables you to create variables that can only be read and written by the same thread. Thus, even if two threads are executing the same code, and the code has a reference to a ThreadLocal variable, then the two threads cannot see each other’s ThreadLocal variables.

例子:

public class ThreadLocalExample {    public static class MyRunnable implements Runnable {        private ThreadLocal<Integer> threadLocal =               new ThreadLocal<Integer>();        @Override        public void run() {            threadLocal.set( (int) (Math.random() * 100D) );            try {                Thread.sleep(2000);            } catch (InterruptedException e) {            }            System.out.println(threadLocal.get());        }    }    public static void main(String[] args) {        MyRunnable sharedRunnableInstance = new MyRunnable();        Thread thread1 = new Thread(sharedRunnableInstance);        Thread thread2 = new Thread(sharedRunnableInstance);        thread1.start();        thread2.start();        thread1.join(); //wait for thread 1 to terminate        thread2.join(); //wait for thread 2 to terminate    }}

15.Thread Signaling

The purpose of thread signaling is to enable threads to send signals to each other. Additionally, thread signaling enables threads to wait for signals from other threads. For instance, a thread B might wait for a signal from thread A indicating that data is ready to be processed.

线程之间等待或者发送信号。

Signaling via Shared Objects

使用共享对象来传递数据

public class MySignal{  protected boolean hasDataToProcess = false;  public synchronized boolean hasDataToProcess(){    return this.hasDataToProcess;  }  public synchronized void setHasDataToProcess(boolean hasData){    this.hasDataToProcess = hasData;    }}

注意上述代码这样简单的赋值和读取都要用synchronized,因为计算机架构中每个线程有不同的cache和register,使用volatile和synchronized来使其同步。

Busy Wait

protected MySignal sharedSignal = ......while(!sharedSignal.hasDataToProcess()){  //do nothing... busy waiting}

wait(), notify() and notifyAll()

Busy waiting is not a very efficient utilization of the CPU in the computer running the waiting thread.

A thread that calls wait() on any object becomes inactive until another thread calls notify() on that object. In order to call either wait() or notify the calling thread must first obtain the lock on that object(先要取得该对象的锁). In other words, the calling thread must call wait() or notify() from inside a synchronized block. Here is a modified version of MySignal called MyWaitNotify that uses wait() and notify().

public class MonitorObject{}public class MyWaitNotify{  MonitorObject myMonitorObject = new MonitorObject();  public void doWait(){    synchronized(myMonitorObject){      try{        myMonitorObject.wait();      } catch(InterruptedException e){...}    }  }  public void doNotify(){    synchronized(myMonitorObject){      myMonitorObject.notify();    }  }}

Once a thread calls wait() it releases the lock it holds on the monitor object(调用wait方法会释放该对象上的锁). This allows other threads to call wait() or notify() too, since these methods must be called from inside a synchronized block.

Missed Signals

The methods notify() and notifyAll() do not save the method calls to them in case no threads are waiting when they are called. The notify signal is then just lost.

关于信号缺失参加以前的并发文章。

Spurious Wakeups

For inexplicable reasons it is possible for threads to wake up even if notify() and notifyAll() has not been called. This is known as spurious wakeups. Wakeups without any reason.不用notify或者notifyAll也可以wakeup线程!好吃惊!

To guard against spurious wakeups the signal member variable is checked inside a while loop instead of inside an if-statement(用while而不用if). Such a while loop is also called a spin lock. The thread awakened spins around until the condition in the spin lock (while loop) becomes false. Here is a modified version of MyWaitNotify2 that shows this:

public class MyWaitNotify3{  MonitorObject myMonitorObject = new MonitorObject();  boolean wasSignalled = false;  public void doWait(){    synchronized(myMonitorObject){      while(!wasSignalled){        try{          myMonitorObject.wait();         } catch(InterruptedException e){...}      }      //clear signal and continue running.      wasSignalled = false;    }  }  public void doNotify(){    synchronized(myMonitorObject){      wasSignalled = true;      myMonitorObject.notify();    }  }}

Multiple Threads Waiting for the Same Signals

Don’t call wait() on constant String’s or global objects

有这样一个类:

public class MyWaitNotify{ //错误示范  String myMonitorObject = "";  boolean wasSignalled = false;  public void doWait(){    synchronized(myMonitorObject){      while(!wasSignalled){        try{          myMonitorObject.wait();         } catch(InterruptedException e){...}      }      //clear signal and continue running.      wasSignalled = false;    }  }  public void doNotify(){    synchronized(myMonitorObject){      wasSignalled = true;      myMonitorObject.notify();    }  }}

The problem with calling wait() and notify() on the empty string, or any other constant string is, that the JVM/Compiler internally translates constant strings into the same object.

That means, that even if you have two different MyWaitNotify instances, they both reference the same empty string instance.

上述错误情况示意图:
这里写图片描述

So: Don’t use global objects, string constants etc. for wait() / notify() mechanisms. Use an object that is unique to the construct using it. For instance, each MyWaitNotify3 (example from earlier sections) instance has its own MonitorObject instance rather than using the empty string for wait() / notify() calls.

16.Deadlock

一些简单的死锁就不说了。

Database Deadlocks

数据库:

Transaction 1, request 1, locks record 1 for updateTransaction 2, request 1, locks record 2 for updateTransaction 1, request 2, tries to lock record 2 for update.Transaction 2, request 2, tries to lock record 1 for update.

17.Deadlock Prevention

防止死锁

Lock Ordering

Lock ordering is a simple yet effective deadlock prevention mechanism. However, it can only be used if you know about all locks needed ahead of taking any of the locks. This is not always the case.

Lock Timeout

Thread 1 locks AThread 2 locks BThread 1 attempts to lock B but is blockedThread 2 attempts to lock A but is blockedThread 1's lock attempt on B times outThread 1 backs up and releases A as wellThread 1 waits randomly (e.g. 257 millis) before retrying.Thread 2's lock attempt on A times outThread 2 backs up and releases B as wellThread 2 waits randomly (e.g. 43 millis) before retrying.

Deadlock Detection

这里写图片描述

解决方法:
1.One possible action is to release all locks, backup, wait a random amount of time and then retry.

2.A better option is to determine or assign a priority of the threads so that only one (or a few) thread backs up. The rest of the threads continue taking the locks they need as if no deadlock had occurred. If the priority assigned to the threads is fixed, the same threads will always be given higher priority. To avoid this you may assign the priority randomly whenever a deadlock is detected.

18.Starvation and Fairness

Causes of Starvation in Java

The following three common causes can lead to starvation of threads in Java:

1.Threads with high priority swallow all CPU time from threads with lower priority.
2.Threads are blocked indefinately waiting to enter a synchronized block, because other threads are constantly allowed access before it.
3.Threads waiting on an object (called wait() on it) remain waiting indefinitely because other threads are constantly awakened instead of it.

Implementing Fairness in Java

1.Using Locks Instead of Synchronized Blocks

public class Synchronizer{  public synchronized void doSynchronized(){    //do a lot of work which takes a long time  }}

改写成:

public class Synchronizer{  Lock lock = new Lock();  public void doSynchronized() throws InterruptedException{    this.lock.lock();      //critical section, do a lot of work which takes a long time    this.lock.unlock();  }}

一个Lock的简单实现:

public class Lock{  private boolean isLocked      = false;  private Thread  lockingThread = null;  public synchronized void lock() throws InterruptedException{    while(isLocked){      wait();    }    isLocked      = true;    lockingThread = Thread.currentThread();  }  public synchronized void unlock(){    if(this.lockingThread != Thread.currentThread()){      throw new IllegalMonitorStateException(        "Calling thread has not locked this lock");    }    isLocked      = false;    lockingThread = null;    notify();  }}

但是:the Lock class makes no different guarantees with respect to fairness than synchronized version of doSynchronized(). But we can change that.

改进一下就能fair:

The current version of the Lock class calls its own wait() method. If instead each thread calls wait() on a separate object, so that only one thread has called wait() on each object, the Lock class can decide which of these objects to call notify() on, thereby effectively selecting exactly what thread to awaken.

A Fair Lock

参考Nested Monitor Lockout和Slipped Conditions写出下列代码,先看看上面参考吧:

public class FairLock {    private boolean           isLocked       = false;    private Thread            lockingThread  = null;    private List<QueueObject> waitingThreads =            new ArrayList<QueueObject>();  public void lock() throws InterruptedException{    QueueObject queueObject           = new QueueObject();    boolean     isLockedForThisThread = true;    synchronized(this){        waitingThreads.add(queueObject);    }    while(isLockedForThisThread){      synchronized(this){        isLockedForThisThread =            isLocked || waitingThreads.get(0) != queueObject;        if(!isLockedForThisThread){          isLocked = true;           waitingThreads.remove(queueObject);           lockingThread = Thread.currentThread();           return;         }      }      try{        queueObject.doWait();      }catch(InterruptedException e){        synchronized(this) { waitingThreads.remove(queueObject); }        throw e;      }    }  }  public synchronized void unlock(){    if(this.lockingThread != Thread.currentThread()){      throw new IllegalMonitorStateException(        "Calling thread has not locked this lock");    }    isLocked      = false;    lockingThread = null;    if(waitingThreads.size() > 0){      waitingThreads.get(0).doNotify();    }  }}

QueueObject:

public class QueueObject {  private boolean isNotified = false;  public synchronized void doWait() throws InterruptedException {    while(!isNotified){        this.wait();    }    this.isNotified = false;  }  public synchronized void doNotify() {    this.isNotified = true;    this.notify();  }  public boolean equals(Object o) {    return this == o;  }}

19.Nested Monitor Lockout

How Nested Monitor Lockout Occurs

Nested monitor lockout is a problem similar to deadlock:

Thread 1 synchronizes on AThread 1 synchronizes on B (while synchronized on A)Thread 1 decides to wait for a signal from another thread before continuingThread 1 calls B.wait() thereby releasing the lock on B, but not A.Thread 2 needs to lock both A and B (in that sequence)        to send Thread 1 the signal.Thread 2 cannot lock A, since Thread 1 still holds the lock on A.Thread 2 remain blocked indefinately waiting for Thread1        to release the lock on AThread 1 remain blocked indefinately waiting for the signal from        Thread 2, thereby        never releasing the lock on A, that must be released to make        it possible for Thread 2 to send the signal to Thread 1, etc.

下面实现代码:

//lock implementation with nested monitor lockout problempublic class Lock{  protected MonitorObject monitorObject = new MonitorObject();  protected boolean isLocked = false;  public void lock() throws InterruptedException{    synchronized(this){      while(isLocked){        synchronized(this.monitorObject){            this.monitorObject.wait();        }      }      isLocked = true;    }  }  public void unlock(){    synchronized(this){      this.isLocked = false;      synchronized(this.monitorObject){        this.monitorObject.notify();      }    }  }}

解析:

Notice how the lock() method first synchronizes on “this”, then synchronizes on the monitorObject member. If isLocked is false there is no problem. The thread does not call monitorObject.wait(). If isLocked is true however, the thread calling lock() is parked waiting in the monitorObject.wait() call.

The problem with this is, that the call to monitorObject.wait() only releases the synchronization monitor on the monitorObject member, and not the synchronization monitor associated with “this”(没有释放在this上的锁). In other words, the thread that was just parked waiting is still holding the synchronization lock on “this”.

When the thread that locked the Lock in the first place tries to unlock it by calling unlock() it will be blocked trying to enter the synchronized(this) block in the unlock() method. It will remain blocked until the thread waiting in lock() leaves the synchronized(this) block. But the thread waiting in the lock() method will not leave that block until the isLocked is set to false, and a monitorObject.notify() is executed, as it happens in unlock().

Put shortly, the thread waiting in lock() needs an unlock() call to execute successfully for it to exit lock() and the synchronized blocks inside it. But, no thread can actually execute unlock() until the thread waiting in lock() leaves the outer synchronized block.

This result is that any thread calling either lock() or unlock() will become blocked indefinately. This is called a nested monitor lockout.

A More Realistic Example

在实际中,注意这种嵌套的synchronized块,wait只能释放掉调用它的对象的锁,并不能够释放掉其他的锁,比如上面的this。

Nested Monitor Lockout vs. Deadlock

跟死锁不同的是,死锁可以通过锁相同的顺序来避免。但是Nested Monitor Lockout就是相同的顺序啊。

Thread 1 locks A and B, then releases B and waits for a signal from Thread 2. Thread 2 needs both A and B to send Thread 1 the signal. So, one thread is waiting for a signal, and another for a lock to be released.

20.Slipped Conditions

What is Slipped Conditions?

Slipped conditions means, that from the time a thread has checked a certain condition until it acts upon it, the condition has been changed by another thread so that it is errornous for the first thread to act. Here is a simple example:

简单就是A检查条件没有问题了,准备执行响应操作,这时候B改变了条件,导致A执行操作出错。比如下列:

public class Lock {    private boolean isLocked = true;    public void lock(){      synchronized(this){        while(isLocked){          try{            this.wait();          } catch(InterruptedException e){            //do nothing, keep waiting          }        }      }      synchronized(this){        isLocked = true;      }    }    public synchronized void unlock(){      isLocked = false;      this.notify();    }}

Notice how the lock() method contains two synchronized blocks. The first block waits until isLocked is false. The second block sets isLocked to true, to lock the Lock instance for other threads.问题主要是出现在lock方法中的两个同步块之间。

In other words, the condition has slipped from the time the condition was checked until the threads change it for subsequent threads.
本质原因:检查和更改值的操作分离了!

A More Realistic Example

关于Fair Lock需要再细细看啊。

23.Reentrance Lockout

关于reentrance的问题。

26.Thread Pools

28.Anatomy of a Synchronizer

Even if many synchronizers (locks, semaphores, blocking queue etc.) are different in function, they are often not that different in their internal design. 原理都差不多,要寻找它们的共同点。

The purpose of most (if not all) synchronizers is to guard some area of the code (critical section) from concurrent access by threads. To do this the following parts are often needed in a synchronizer:

1.State
2.Access Condition
3.State Changes
4.Notification Strategy
5.Test and Set Method
6.Set Method

State

The state of a synchronizer is used by the access condition to determine if a thread can be granted access.

1.In a Lock the state is kept in a boolean saying whether the Lock is locked or not.
2.In a Bounded Semaphore the internal state is kept in a counter (int) and an upper bound (int) which state the current number of “takes” and the maximum number of “takes”.
3.In a Blocking Queue the state is kept in the List of elements in the queue and the maximum queue size (int) member (if any).

Access Condition

The access conditions is what determines if a thread calling a test-and-set-state method can be allowed to set the state or not.
例如:

public class BoundedSemaphore {  private int signals = 0;  private int bound   = 0;  public BoundedSemaphore(int upperBound){    this.bound = upperBound;  }  public synchronized void take() throws InterruptedException{    //access condition    while(this.signals == bound) wait();    this.signals++;    this.notify();  }  public synchronized void release() throws InterruptedException{    //access condition    while(this.signals == 0) wait();    this.signals--;    this.notify();  }}

State Changes

Once a thread gains access to the critical section it has to change the state of the synchronizer to (possibly) block other threads from entering it. In other words, the state needs to reflect the fact that a thread is now executing inside the critical section. This should affect the access conditions of other threads attempting to gain access.

进去了一趟,总得改变下状态吧。

public class BoundedSemaphore {  private int signals = 0;  private int bound   = 0;  public BoundedSemaphore(int upperBound){    this.bound = upperBound;  }  public synchronized void take() throws InterruptedException{    while(this.signals == bound) wait();    //state change    this.signals++;    this.notify();  }  public synchronized void release() throws InterruptedException{    while(this.signals == 0) wait();    //state change    this.signals--;    this.notify();  }}

Notification Strategy

Once a thread has changed the state of a synchronizer it may sometimes need to notify other waiting threads about the state change. Perhaps this state change might turn the access condition true for other threads.更新了值需要通知。

3种通知策略:

1.Notify all waiting threads.
2.Notify 1 random of N waiting threads.
3.Notify 1 specific of N waiting thread.

Sometimes you may need to notify a specific rather than a random waiting thread. For instance if you need to guarantee that waiting threads are notified in a specific order, be it the order they called the synchronizer in, or some prioritized order. To achive this each waiting thread must call wait() on its own, separate object. When the notifying thread wants to notify a specific waiting thread it will call notify() on the object this specific thread has called wait() on. An example of this can be found in the text Starvation and Fairness.通知具体的wait线程请参考Starvation and Fairness一节。

Test and Set Method

Set Method

31.Java Concurrency References

最后推荐更多的参考资料Java Concurrency References

0 0
原创粉丝点击