并发问题不再是一个只有高级程序员才能接触的问题了,在使用多线程编程的时候,咱们更多的将目光放在追求系统的高并发和高吞吐,而这一切的前提是确保程序的正确性。在多线程编程中容易产生的问题有不少,好比线程安全问题、死锁、饥饿等。下面的例子将从线程安全开始,逐步开启并发编程的大门。 java
@NotThreadSafe public class UnsafeSequence { private int value; /** Returns a unique value. */ public int getNext() { return value++; } }
这个例子来源于《Java Concurrency In Practice》的第一章,一个最最简单,却容易引发线程安全问题的片断。书中给出了以下解释:The problem with UnsafeSequence is that with some unlucky timing, two threads could call getNext and receive the same value. Figure 1.1 shows how this can happen. The increment notation, nextValue++, may appear to be a single operation, but is in fact three separate operations: read the value, add one to it, and write out the new value. Since operations in multiple threads may be arbitrarily interleaved by the runtime, it is possible for two threads to read the value at the same time, both see the same value, and then both add one to it. The result is that the same sequence number is returned from multiple calls in different threads. 程序员
Figure 1.1的确很明了的告诉咱们线程推动的过程,这也给了咱们一个寻找线程不安全的方法,经过这样图示法来分析问题。 编程
固然,这里引出了第一个可能会引发线程不安全的因素:程序中有变量的读取、写入或判断操做。 安全
/**例如上述变量的自增*/ public int getNext() { return value++; } /**例如单例模式中队变量的判断操做*/ Public Object getInstance(){ If(obj==null){ return new Object(); } return obj; }
写一个简单的程序验证一下上述问题,其实线程的学习最好的办法就是举例证实线程的不安全性,而后再想出解决方案,固然这也多是这部分学习最难所在: 多线程
package com.a2.concurrency.chapter1; /** * 线程安全第一种因素:程序中有变量的读取、写入或判断操做 * @author ChenHui * */ public class UnsafeSequence { private int value; public int getValue() { return value++; } public static void main(String[] args) throws InterruptedException { final UnsafeSequence us = new UnsafeSequence(); Thread th1 = new Thread("th1") { @Override public void run() { System.out.println( us.getValue()+" "+super.getName()); } }; Thread th2 = new Thread("th2") { @Override public void run() { System.out.println(us.getValue()+" "+super.getName()); } }; th1.start(); /** * 若是不執行Thread.sleep(1000); * 偶尔結果为: * 0 th2 * 0 th1 * 若是执行Thread.sleep(1000); * 结果为: * 0 th1 * 1 th2 */ //Thread.sleep(1000); th2.start(); } }
对于这种因素产生的问题,咱们先给出一种经常使用解决方案,就是使用同步机制。这里咱们先给出最简单,你们也最容易想到的方案,对操做加synchronized关键字: 并发
private volatile int value; public synchronized int getNext() { return value++; }
在这里使用了synchronized的状况下,是否使用volatile关键字并非主要的。 app
这一节的最后例举一下通常会遇到线程安全问题的地方,引用自并发编程书中第一章: ide
l Timer 函数
l Servlet/JSP 高并发
l RMI
l Swing
l ……
注:synchronized 方法控制对类成员变量的访问: 每一个类实例对应一把锁,每一个 synchronized 方法都必须得到调用该方法的类实例的锁方能执行,不然所属 线程阻塞 ,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能得到该锁,从新进入可 执行状态。这种机制确保了同一时刻对于每个类实例,其全部声明为 synchronized 的成员函数中至多只有一个处于可执行状态(由于至多只有一个可以得到该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要全部可能访问类成员变量的方法均被声明为 synchronized)。