第一章Java并发编程实践基础
进程通常由三部分组成。一部分是程序,一部分数据集合,另一部分被称为进程控制块(ProcessControlBlock,简记PCB)。
package simplethread;
public class MyThread1 extends Thread {
public MyThread1(String name) {
super(name);//传递线程的名字
}
public static void main(String[] args) {
// TODO Auto-generated method stub
for (int i = 0; i < 5; i++) { //创建5个线程
new MyThread1("thread" + i).start();
}
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {//输出线程名字和i
System.out.println(this.getName() + ":" + i);
}
}
}
1.2.3 线程池
JDK提供的线程池一般分为3步:1)创建线程目标对象,可以是不同的,例如程序中的Runnner;2)使用Executors创建线程池,返回一个ExecutorService类型的对象;3)使用线程池执行线程目标对象,exec.execute(run),最后,结束线程池中的线程,exec.shutdown()。
package threadPoolTest;
import java.util.concurrent.ExecutorService; //创建新线程的线程池
import java.util.concurrent.Executors;
public class TestThreadPool {
public static void main(String args[]) throws InterruptedException {
// 在线程池中创建2个线程 -> 创建一个可重用固定线程数的线程池
ExecutorService exec = Executors.newFixedThreadPool(2);
// 创建100个线程目标对象
for (int index = 0; index < 100; index++) {
Runnable run = new Runner(index);
// 执行线程目标对象
exec.execute(run);
}
// shutdown
exec.shutdown();
}
}
// 线程目标对象
class Runner implements Runnable {
int index = 0;
public Runner(int index) {
this.index = index;
}
@Override
public void run() {
long time = (long) (Math.random() * 1000);
// 输出线程的名字和使用目标对象及休眠的时间
System.out.println("线程:" + Thread.currentThread().getName() + "(目标对象" + index + ")" + ":Sleeping " + time + "ms");
try {
Thread.sleep(time);
} catch (InterruptedException e) { }
}
}
1.3 线程的基本控制
package threadJoinTest;
public class ThreadJoin extends Thread {
static int result = 0;
public ThreadJoin(String name) {
super(name);
}
public static void main(String[] args) {
System.out.println("主线程执行");
Thread t = new ThreadJoin("计算线程");
t.start();
System.out.println("result:" + result);
try {
long start = System.nanoTime();
t.join(); //wait for t finish
long end = System.nanoTime();
System.out.println((end - start) / 1000000 + "毫秒后:" + result);
} catch (InterruptedException e) {
e.printStackTrace(); //打印异常栈轨迹Stack Trace
}
}
@Override
public void run() {
System.out.println(this.getName() + "开始计算...");
try {
Thread.sleep(4000);
System.out.println("run is over");
} catch (InterruptedException e) {
e.printStackTrace();
}
result = (int) (Math.random() * 10000);
System.out.println(this.getName() + "结束计算:");
}
}
第二章构建线程安全应用程序
如果其他线程企图访问一个处于不可用状态的对象,该对象将不能正确响应从而产生无法预料的结果,如何避免这种情况发生是线程安全性的核心问题
一个类在可以被多个线程安全调用时就是线程安全的
2.2. Servlet的线程安全性
- 无状态Servlet
2 有状态Servlet
import java.io.PrintWriter; //打印格式化对象的表示到文本输出流
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class webTest
*/
@WebServlet("/webTest")
public class webTest extends HttpServlet {
private static final long serialVersionUID = 1L;
int result = 0;
/**
* @see HttpServlet#HttpServlet()
*/
public webTest() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
String s1 = request.getParameter("num1");
String s2 = request.getParameter("num2");
if (s1 != null && s1 != null) {
result = Integer.parseInt(s1) * Integer.parseInt(s2);
} try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
PrintWriter out = response.getWriter();
out.print(result);
out.close();
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
}
}
运行
http://localhost:8080/test/StatefulServlet?num1=5&num2=80
要解决线程不安全性,其中一个主要的方法就是取消Servlet的实例变量,变成无状态的Servlet。另外一种方法是对共享数据进行同步操作。使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段
2.3. 同步与互斥
2.4. 同步与volatile
在确定内存访问如何排序以及合适,可以确保他们可见时所使用的规则被称为Java编程语言的内存模型
2.5. 活性
2.6. ThreadLocal变量
,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
JDK 5以后提供了泛型支持,ThreadLocal被定义为支持泛型:
public class ThreadLocal<T> extends Object
ThreadLocal vs 线程同步机制
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
2.7. 高级并发对象
第三章 使用JDK并发包构建程序
3.1 java.util.concurrent概述
3.2 原子量
是无锁算法(nonblocking algorithms),这些无锁算法使用低层原子化的机器指令,例如使用compare-and-swap(CAS)代替锁保证并发情况下数据的完整性
3.2.1 锁同步法
3.2.2 比较并交换
比较并交换(Compare And Swap)”或 CAS 的原语
CAS 操作包含三个操作数—— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作
说明CAS的工作原理(为了便于说明,用同步语法表示)。
package jdkapidemo;
public class SimulatedCAS {
private int value;
public synchronized int getValue() {
return value;
}
public synchronized int compareAndSwap(int expectedValue, int newValue) {
if (value == expectedValue)
value = newValue;
return value;
}
}
乐观并发控制
基于 CAS 的并发算法称为“无锁定算法”,因为线程不必再等待锁定(有时称为互斥或关键部分,这取决于线程平台的术语)。无论 CAS 操作成功还是失败,在任何一种情况中,它都在可预知的时间内完成。如果 CAS 失败,调用者可以重试 CAS 操作或采取其他适合的操作。
3.3 并发集合
3.3.1 队列Queue与BlockingQueue
3.4 同步器
3.4.1 Semaphore
3.4.2 Barrier
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class cyclicBarrierTest {
// 徒步需要的时间: Shen
private static int[] timeForWalk = { 5, 8, 15 };
//自驾游
private static int[] timeForSelf = { 1, 3, 4 };
// 旅游大巴
private static int[] timeForBus = {2, 4, 6};
//时间格式化
static String nowTime(){
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
return sdf.format(new Date()) + ": ";
}
static class Tour implements Runnable {
private int[] m_timeForUse;
//一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)
private CyclicBarrier m_barrier;
private String m_tourName;
public Tour(CyclicBarrier barrier, String tourName, int[] timeUsed) {
this.m_timeForUse = timeUsed;
this.m_tourName = tourName;
this.m_barrier = barrier;
}
public void run() {
try{
Thread.sleep(m_timeForUse[0] * 1000);
System.out.println(nowTime() + m_tourName + "Reached Shenzhen");
m_barrier.await();//
Thread.sleep(m_timeForUse[1] * 1000);
System.out.println(nowTime() + m_tourName + "Reached Guangzhou");
m_barrier.await();//到达中转
Thread.sleep(m_timeForUse[2] * 1000);
System.out.println(nowTime() + m_tourName + " Reached Chongqing");
m_barrier.await();
} catch (InterruptedException e) {
} catch (BrokenBarrierException e) {
}
}
}
public static void main(String[] args) { // 三个旅行
Runnable runner = new Runnable() {
//@Override是伪代码,表示重写
@Override
public void run() {
System.out.println("We all are here.");
}
};
//CyclicBarrier(int,Runnable): 当await的数量到达了设定的数量后,首先执行该Runnable对象
CyclicBarrier barrier = new CyclicBarrier(3,runner);
//使用线程池
ExecutorService exec = Executors.newFixedThreadPool(3);
exec.submit(new Tour(barrier, "WalkTour", timeForWalk));
exec.submit(new Tour(barrier, "SelfTour", timeForSelf));
exec.submit(new Tour(barrier, "BusTour", timeForBus));
exec.shutdown();
}
}
3.4.3 CountDownLatch
import java.util.concurrent.CountDownLatch;
public class Player implements Runnable {
private int id;
private CountDownLatch begin;
private CountDownLatch end;
public Player(int i, CountDownLatch begin, CountDownLatch end) {
// TODO Auto-generated constructor stub
super();
this.id = i;
this.begin = begin;
this.end = end;
}
@Override
public void run() {
// TODO Auto-generated method stub
try{
begin.await(); //等待begin的状态为0
Thread.sleep((long)(Math.random()*100)); //随机分配时间,即运动员完成时间
System.out.println("Play"+id+" arrived.");
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}finally{
end.countDown(); //使end状态减1,最终减至0
}
}
}
/**
CountDownLatch类是一个同步计数器,构造时传入int参数,该参数就是计数器的初始值,
每调用一次countDown()方法,计数器减1,计数器大于0 时,await()方法会阻塞程序继续执行
CountDownLatch如其所写,是一个倒计数的锁存器,当计数减至0时触发特定的事件。
利用这种特性,可以让主线程等待子线程的结束。下面以一个模拟运动员比赛的例子加以说明。
*/
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountLatch {
private static final int PLAYER_AMOUNT = 5;
public CountLatch() {
}
public static void main(String[] args) {
// TODO Auto-generated method stub
//对于每位运动员,CountDownLatch减1后即结束比赛
CountDownLatch begin = new CountDownLatch(1);
//对于整个比赛,所有运动员结束后才算结束
CountDownLatch end = new CountDownLatch(PLAYER_AMOUNT);
Player[] plays = new Player[PLAYER_AMOUNT];
for(int i=0;i<PLAYER_AMOUNT;i++)
plays[i] = new Player(i+1,begin,end);
//设置特定的线程池,大小为5
ExecutorService exe = Executors.newFixedThreadPool(PLAYER_AMOUNT);
for(Player p:plays)
exe.execute(p); //分配线程
System.out.println("Race begins!");
begin.countDown(); //启动命令
try{
end.await(); //等待end状态变为0,即为比赛结束
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}finally{
System.out.println("Race ends!");
}
exe.shutdown();
}
}
3.4.5 Future和Future work
接口public interface Future<V>
3.5 显示锁
3.5.1 ReentrantLock
3.6 Fork-Join框架
关于线程池,参考 http://www.cnblogs.com/zrtqsk/p/3784049.html
http://www.cnblogs.com/zrtqsk/p/3776328.html
if (当前这个任务工作量足够小)
直接完成这个任务
else
将这个任务或这部分工作分解成两个部分
分别触发(invoke)这两个子任务的执行,并等待结果
Java7引入了Fork Join的概念,而此书成书较早,尚无正式Fork-join示例。
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
public class ForkJoinDemo {
public static void main(String[] args) {
long beginTime = System.nanoTime();
System.out.println("The sum from 1 to 1000 is " + sum(1, 1000));
System.out.println("Time consumed(nano second) By recursive algorithm : " + (System.nanoTime() - beginTime));
beginTime = System.nanoTime();
System.out.println("The sum from 1 to 1000000000 is " + sum1(1, 1000000000));
System.out.println("Time consumed(nano second) By loop algorithm : " + (System.nanoTime() - beginTime));
ForkJoinDemo app = new ForkJoinDemo();
//orkJoinPool是一个可以执行ForkJoinTask的ExcuteService,
//与ExcuteService不同的是它采用了work-stealing模式:
//所有在池中的线程尝试去执行其他线程创建的子任务,这样就很少有线程处于空闲状态,非常高效。
ForkJoinPool forkJoinPool = new ForkJoinPool();
//ForkJoinTask代表一个需要执行的任务
CountTask task = app.new CountTask(1,1000000000);
beginTime = System.nanoTime();
Future<Long> result = forkJoinPool.submit(task);
try{
System.out.println("The sum from 1 to 1000000000 is " + result.get());
}
catch(Exception e){
e.printStackTrace();
}
System.out.println("Time consumed(nano second) By ForkJoin algorithm : " + (System.nanoTime() - beginTime));
}
private static long sum1(long start, long end) {
long s = 0l;
for(long i=start; i<= end; i++){
s += i;
}
return s;
}
private static long sum(long start, long end){
if(end > start){
return end + sum(start, end-1);
}
else{
return start;
}
}
//待执行任务
private class CountTask extends RecursiveTask<Long>{
private static final int THRESHOLD = 10000;
private int start;
private int end;
public CountTask(int start, int end){
this.start = start;
this.end = end;
}
//重载 protected void compute() 方法
protected Long compute(){
//System.out.println("Thread ID: " + Thread.currentThread().getId());
Long sum = 0l;
if((end -start) <= THRESHOLD){
sum = sum1(start, end);
}
else{
int middle = (start + end) / 2;
CountTask leftTask = new CountTask(start, middle);
CountTask rightTask = new CountTask(middle + 1, end);
leftTask.fork();
rightTask.fork();
Long leftResult = leftTask.join();
Long rightResult = rightTask.join();
sum = leftResult + rightResult;
}
return sum;
}
}
}
第4章 使用开源软件构建并发应用程序
4.1 开源软件Amino介绍
Amino是Apache旗下的开源软件。http://amino-cbbs.sourceforge.net/
4.4 Amino使用的模式和调度算法
在Amino开源代码中提供了Master-Worker工厂摸式
第5章 数据冲突及诊断工具MTRAT
Java的数据有两种基本类型内存分配模式(不算虚拟机内部类型,详细内容参见虚拟机规范):运行时栈和堆两种。由于运行时栈是线程所私有的,它主要用来保存局部变量和中间运算结果,因此它们的数据是不可能被线程之间所共享的。内存堆是创建类对象和数组地方,它们是被虚拟机内各个线程所共享的,因此如果一个线程能获得某个堆对象的引用,那么就称这个对象是对该线程可见的。
编写线程安全的代码,本质上就是管理对状态(state)的访问,而且通常这些状态都是共享的、可变的。
5.1.5 ThreadLocal
5.2使用阻塞队列的生产者-消费者模式
5.3 MTRAT介绍
Mtrat软件下载网址是http://www.alphaworks.ibm.com/tech/mtrat
第6章 死锁
6.4减小锁的竞争和粒度
6.5使用MTRAT诊断死锁
第七章 显示锁
7.3 Lock与Condition
第八章原子变量与非阻塞算法
9 Java内存模型
此章几乎没内容
9.1.2 发生前关系(happen-before)
9.2 初始化安全性