对Java 7 并发编程指南中文版 学习总结的笔记 方便以后回忆总结
原文如下: http://ifeve.com/java-7-concurrency-cookbook/
第一章: 线程管理
线程管理(一)线程的创建和运行
一种是extends Thread,重写run方法,然后调用new Thread().start() 另一种是implements Runnable,重写run方法,然后调用new Thread(new Runnable()).start()
其实第一种就是对第二种多进行了一层封装,第二种是针对已经extends其他类的情况来用的
线程管理(二)获取和设置线程信息
线程有以下几个属性 ID: 每个线程的独特标识。 Name: 线程的名称。 Priority: 线程对象的优先级。优先级别在1-10之间,1是最低级,10是最高级。不建议改变它们的优先级,但是你想的话也是可以的。 Status: 线程的状态。在Java中,线程只能有这6种中的一种状态: new, runnable, blocked, waiting, time waiting, 或 terminated.
其中只有Name和Priority可以设置值,ID和Status为线程自己生成的
线程管理(三)线程的中断
Thread.interrupt() 这个中断会给人一种终止运行的感觉,但其实core java中是这么描述的"没有任何语言方面的需求要求一个被中断的程序应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断"
这里有两种情况 1、thread正在Object.wait, Thread.join和Thread.sleep,会抛出java.lang.InterruptedException: 线程退出(可想而知,没有占用CPU运行的线程是不可能给自己的中断状态置位的。这就会产生一个InterruptedException异常。) 2、thread正在正常运行,是不会中断退出的,但是可以通过isInterrupted()获取状态自主判断是否退出
所以Thread.interrupt() 其实是中断那些阻塞的线程的,因为阻塞线程回去检查中断标志位,有中断就抛异常来结束线程的运行。而对于正常运行的线程,需要自己进行判断。
这里可以参考http://blog.csdn.net/budapest/article/details/6941802 描述的很好理解。
PS一下还有个interrupted()方法,返回值跟isInterrupted()一样,不过它还会把中断状态给清除了
线程管理(四)操作线程的中断机制
这个我理解是可以通过判断中断状态,抛出一个异常来提前结束一些递归调用或者复杂逻辑调用 方式如下:
if (Thread.interrupted()) {
throw new InterruptedException();
}
线程管理(五)线程的睡眠和恢复
可以通过调用Thread.sleep(long millis)或者TimeUnit来进行对线程的睡眠。 线程睡眠是强制当前正在执行的线程休眠(暂停执行),以“减慢线程”,通常是线程运行的太快,或者是在调试的时候更容易发现错误使用。
PS:这里要补充说明下java线程运行的状态,来说明sleep的原理,参考JDK Thread中枚举对象State的状态说明
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
这篇文章中给出了英文的解释,以及各个状态的例子,非常详细。 http://fangjian0423.github.io/2016/06/04/java-thread-state/
也就是说当现场调用了sleep方法之后,它就会退出占用CPU的运行,状态从RUNNABLE 变为 TIMED_WAITING 而当sleep的时间到达后,线程重新从TIMED_WAITING 变为 RUNNABLE 状态,从而可以占用到cpu时间片的调度中。
PPS:建议使用TimeUnit来进行线程的sleep,TimeUnit本质还是调用了Thread.sleep()方法,但是TimeUnit可以通过TimeUnit.SECONDS.sleep(),TimeUnit.MINUTES.sleep()等方法增加程序的易读性。
线程管理(六)等待线程的终结
可以使用Thread 类的join() 方法。当前线程调用某个线程的这个方法时,它会暂停当前线程,直到被调用线程执行完成。 比如在主线程中
Thread thread = new ThreadTest(); // 比如资源初始化的优先级比较高的线程
thread.start(); // 运行线程
thread.join(); // 当前线程进入WAITING状态,等待thread运行结束后,当前线程再变成RUNNABLE状态重新运行
// 或者是一个带时间的join,表示thread结束或者时间到了,当前线程都会重新运行
// 如果是带时间参数的join,那么当前线程进入的是TIMED_WAITING状态
// thread.join (long milliseconds)
通过join可以在一定程度上控制线程之间的同步状态,后面会有更多同步状态的办法。
线程管理(七)守护线程的创建和运行
Java有一种特别的线程叫做守护线程。这种线程的优先级非常低,通常在程序里没有其他线程运行时才会执行它。当守护线程是程序里唯一在运行的线程时,JVM会结束守护线程并终止程序。
根据这些特点,守护线程通常用于在同一程序里给普通线程(也叫使用者线程)提供服务。它们通常无限循环的等待服务请求或执行线程任务。它们不能做重要的任务,因为我们不知道什么时候会被分配到CPU时间片,并且只要没有其他线程在运行,它们可能随时被终止。JAVA中最典型的这种类型代表就是垃圾回收器。
通过thread.setDaemon(true)来设置线程为守护线程,用到的不多。
线程管理(八)在线程里处理不受控制的异常
Java里有2种异常: 检查异常(Checked exceptions): 这些异常必须强制捕获它们或在一个方法里的throws子句中。 例如, IOException 或者ClassNotFoundException。 未检查异常(Unchecked exceptions): 这些异常不用强制捕获它们。例如, NumberFormatException。
在一个线程 对象的 run() 方法里抛出一个检查异常,我们必须捕获并处理他们。因为 run() 方法不接受 throws 子句。当一个非检查异常被抛出,默认的行为是在控制台写下stack trace并退出程序,但是可以通过thread.setUncaughtExceptionHandler(UncaughtExceptionHandler eh)来进行未受检异常的捕获处理。 例如
Thread thread1 = new Thread1();
thread1.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
System.out.printf("An exception has been captured\n");
System.out.printf("Thread: %s\n", t.getId());
System.out.printf("Exception: %s: %s\n", e.getClass().getName(), e.getMessage());
System.out.printf("Stack Trace: \n");
e.printStackTrace(System.out);
System.out.printf("Thread status: %s\n", t.getState());
}
});
线程管理(九)使用本地线程变量
这个是ThreadLocal的使用,为了防止线程之间资源共享而导致的问题,一个解决办法就采用ThreadLocal为每个Thread分配一个独立的资源。
参考以下文章进行ThreadLocal的使用 http://lavasoft.blog.51cto.com/62575/51926/
PS:另外还有个可继承版本的ThreadLocal,叫做InheritableThreadLocal 类提供线程创建线程的值的遗传性 。如果线程A有一个本地线程变量,然后它创建了另一个线程B,那么线程B将有与A相同的本地线程变量值。 你可以覆盖 childValue() 方法来初始子线程的本地线程变量的值。 它接收父线程的本地线程变量作为参数。
线程管理(十)线程组
Java并发 API里有个有趣的方法是把线程分组。这个方法允许我们按线程组作为一个单位来处理。例如,你有一些线程做着同样的任务,你想控制他们,无论多少线程还在运行,他们的状态会被一个call 中断。
Java 提供 ThreadGroup 类来组织线程。 ThreadGroup 对象可以由 Thread 对象组成和由另外的 ThreadGroup 对象组成,生成线程树结构。
主要方法有list()、activeCount()、 enumerate()、waitFinish()、interrupt() 等方法,方便一批同类型线程的操作。 留个地址吧 http://outofmemory.cn/java/java.util.concurrent/ThreadGroup
线程管理(十一)处理线程组内的不受控制异常
可以在ThreadGroup中定义uncaughtException方法,当线程组内的线程有未检查异常时,会调用线程组的uncaughtException方法。 流程如下:主要是针对《线程管理(八)在线程里处理不受控制的异常》线程组内附加的一种方式。
首先, 它寻找这个未捕捉的线程对象的异常handle,如在线程中处理不受控制异常中介绍的,如果这个handle 不存在,那么JVM会在线程对象的ThreadGroup里寻找非捕捉异常的handler,如我们在这个指南中学习的。如果此方法不存在,那么 JVM 会寻找默认非捕捉异常handle。如果没有 handlers存在, 那么 JVM会把异常的 stack trace 写入控制台并结束任务。
线程管理(十二)用线程工厂创建线程
在面向对象编程的世界中,工厂模式是最有用的设计模式。它是一个创造模式,还有它的目的是创建一个或几个类的对象的对象。然后,当我们想创建这些类的对象时,我们使用工厂来代替new操作。
有了这个工厂,我们有这些优势来集中创建对象们:
更简单的改变了类的对象创建或者说创建这些对象的方式。 更简单的为了限制的资源限制了对象的创建。 例如, 我们只new一个此类型的对象。 更简单的生成创建对象的统计数据。 Java提供一个接口, ThreadFactory 接口实现一个线程对象工厂。 并发 API 使用线程工厂来创建线程的一些基本优势。
PS:使用工厂模式,批量的初始化线程的一些公有属性以及做一些限制,原文如下: 在这个例子中创建自定义线程,名字使用特别形式或者继承Java Thread类来创建自己的Thread类。 保存线程创建数据,如之前的例子。 限制线程创建个数。 验证线程的创建。 和你能想到的任何东西。
第二章 : 基本线程同步
基本线程同步(一)引言
在并发编程中发生的最常见的一种情况是超过一个执行线程使用共享资源。在并发应用程序中,多个线程读或写相同的数据或访问同一文件或数据库连接这是正常的。这些共享资源会引发错误或数据不一致的情况,我们必须通过一些机制来避免这些错误。
解决这些问题从临界区的概念开始。临界区是访问一个共享资源在同一时间不能被超过一个线程执行的代码块。
Java(和 几乎所有的编程语言)提供同步机制,帮助程序员实现临界区。当一个线程想要访问一个临界区,它使用其中的一个同步机制来找出是否有任何其他线程执行临界 区。如果没有,这个线程就进入临界区。否则,这个线程通过同步机制暂停直到另一个线程执行完临界区。当多个线程正在等待一个线程完成执行的一个临界 区,JVM选择其中一个线程执行,其余的线程会等待直到轮到它们。
本章展示了一些的指南,指导如何使用Java语言提供的两种基本的同步机制:
关键字synchronized Lock接口及其实现
基本线程同步(二)同步方法
基本线程同步(三)在同步的类里安排独立属性
这两章讲的其实是synchronized方法和synchronized代码块,网上的文章也说了很多了,下面我用自己的理解总结一下: 首先synchronized翻译为"同步",或者叫"同步锁",我更愿意叫做同步锁。 要描述清楚这个synchronized,可以通过现实中的一个例子来说明: 设想一个ATM取款机取钱的场景,一个人进入取款室之后首先会把门锁住,而后面来的人只能等到前一个人操作完,解锁,开门出来之后才能重新进入取款室,而第三个人同样需要按照此流程等待。 为什么需要这样呢,首先我们排除是因为取款隐私这个因素,假如两个人同时进入取款室,前一个人正在操作还没取卡,而后一个人却也要把卡塞进去操作了,现实中第二个人无法把卡塞进去,但是在代码世界,第二个人的卡会把第一个人的卡给覆盖掉,这样第一个人操作到一半的数据就会因为被替换成第二个人的卡而造成异常。所以"上锁"机制是很有必要的,也就是synchronized。
换到代码世界,假如我们想上一个锁应该怎么做呢,我们可以给"对象"上锁(实现是通过Monitor进行的)。
- 首先说synchronized代码块,写法如:
synchronized (this) {
// so something...
}
当锁住的是this时,表示锁住当前类的实例对象(比如锁住的是自己的取款室,旁边的取款室别人仍然可以使用)
private byte[] lock = new byte[0];
synchronized (lock) {
// so something...
}
当锁住的是任意的对象时,表示以申明的对象来进行上锁(这个硬要跟提款机的例子挂上勾,就是拿lock对象来当锁锁门) 这种用法一般是一个对象中对于不同功能进行分别上锁用的,可以申明两个对象分别作为锁,因为如果拿this来锁的话,这两个功能就不能同时进行了,但是用两个对象锁时,只是这两个功能自己内部不能同时运行。 PS:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
synchronized (Foo.class) {
// so something...
}
当锁住的是class对象的时候,表示以class对象进行上锁,因为class是全局唯一的。所以这种情况是针对synchronized (this)而言的,synchronized (this)是每个实例内部上锁,而synchronized (Foo.class)是对所有实例都进行上锁,不管这个有几个实例运行到synchronized (Foo.class)时都会被阻塞住
- 现在来说说synchronized方法 public synchronized void function() // 加在普通方法上,等于给整个方法加上synchronized (this) 等价于
public void function() {
synchronized (this) {
}
}
public synchronized static void function() // 加在static方法上,等于给整个方法加上synchronized (Foo.class) 等价于
public static void function() {
synchronized (Foo.class) {
}
}
PS:同一个线程是可以重复进入synchronized 的,也就是说锁是针对线程级别的。
基本线程同步(四)在同步代码中使用条件
这个针对synchronized获得锁之后的由于条件不满足,而需要释放锁的情况设计的。 典型的,生产者和消费者的例子,生产者有锁,但是队列满了,这个时候就养让步出锁(wait()),而当产生了新的数据之后就要通知消费者来消费(notify()和notifyAll())。
参考 http://ifeve.com/basic-thread-synchronization-4/(原文) http://www.cnblogs.com/techyc/p/3272321.html
PS:notify()和notifyAll()唤醒的是那些想要进入synchronized块,却由于被锁住而进入block的线程,并不是所有的线程。
基本线程同步(五)使用Lock同步代码块
使用Lock同步代码块
Java提供另外的机制用来同步代码块。它比synchronized关键字更加强大、灵活。它是基于Lock接口和实现它的类(如ReentrantLock)。这种机制有如下优势:
它允许以一种更灵活的方式来构建synchronized块。使用synchronized关键字,你必须以结构化方式得到释放synchronized代码块的控制权。Lock接口允许你获得更复杂的结构来实现你的临界区。 Lock 接口比synchronized关键字提供更多额外的功能。新功能之一是实现的tryLock()方法。这种方法试图获取锁的控制权并且如果它不能获取该锁,是因为其他线程在使用这个锁,它将返回这个锁。使用synchronized关键字,当线程A试图执行synchronized代码块,如果线程B正在执行它,那么线程A将阻塞直到线程B执行完synchronized代码块。使用锁,你可以执行tryLock()方法,这个方法返回一个 Boolean值表示,是否有其他线程正在运行这个锁所保护的代码。 当有多个读者和一个写者时,Lock接口允许读写操作分离。 Lock接口比synchronized关键字提供更好的性能。
例子参照原文 http://ifeve.com/basic-thread-synchronization-5/
基本线程同步(六)使用读/写锁同步数据访问
读写分离
使用读/写锁同步数据访问
锁所提供的最重要的改进之一就是ReadWriteLock接口和唯一 一个实现它的ReentrantReadWriteLock类。这个类提供两把锁,一把用于读操作和一把用于写操作。同时可以有多个线程执行读操作,但只有一个线程可以执行写操作。当一个线程正在执行一个写操作,不可能有任何线程执行读操作。
http://ifeve.com/basic-thread-synchronization-6/
基本线程同步(七)修改Lock的公平性
可以打开锁的公平性,先等待的线程将先获得锁。 修改Lock的公平性
在ReentrantLock类和 ReentrantReadWriteLock类的构造器中,允许一个名为fair的boolean类型参数,它允许你来控制这些类的行为。默认值为 false,这将启用非公平模式。在这个模式中,当有多个线程正在等待一把锁(ReentrantLock或者 ReentrantReadWriteLock),这个锁必须选择它们中间的一个来获得进入临界区,选择任意一个是没有任何标准的。true值将开启公平 模式。在这个模式中,当有多个线程正在等待一把锁(ReentrantLock或者ReentrantReadWriteLock),这个锁必须选择它们 中间的一个来获得进入临界区,它将选择等待时间最长的线程。考虑到之前解释的行为只是使用lock()和unlock()方法。由于tryLock()方 法并不会使线程进入睡眠,即使Lock接口正在被使用,这个公平属性并不会影响它的功能。
http://ifeve.com/basic-thread-synchronization-7/
基本线程同步(八)在Lock中使用多个条件
通过lock.newCondition();来获取一个condition对象,然后调用condition.await(),condition.signall()达到wait(),notifyAll()一样的功能。
在Lock中使用多个条件
一个锁可能伴随着多个条件。这些条件声明在Condition接口中。 这些条件的目的是允许线程拥有锁的控制并且检查条件是否为true,如果是false,那么线程将被阻塞,直到其他线程唤醒它们。Condition接口提供一种机制,阻塞一个线程和唤醒一个被阻塞的线程。
在并发编程中,生产者与消费者是经典的问题。我们有一个数据缓冲区,一个或多个数据生产者往缓冲区存储数据,一个或多个数据消费者从缓冲区中取出数据,正如在这一章中前面所解释的一样。
http://ifeve.com/basic-thread-synchronization-8/
第三章: 线程同步工具
一些并发同步辅助类 http://ifeve.com/thread-synchronization-utilities-1/
线程同步工具(一)控制并发访问资源
线程同步工具(二)控制并发访问多个资源
讲了信号量机制,Semaphore机制,跟操作系统的信号量概念一致。 通过Semaphore semaphore=new Semaphore(1); //申请1个单位的资源 semaphore.acquire(); // 使用一个单位的资源,当没有一个单位的资源可以使用时,就会阻塞 semaphore.release(); // 释放资源
The acquire(), acquireUninterruptibly(), tryAcquire(),和release()方法有一个外加的包含一个int参数的版本。这个参数表示 线程想要获取或者释放semaphore的许可数。也可以这样说,这个线程想要删除或者添加到semaphore的内部计数器的单位数量。在这个例子中acquire(), acquireUninterruptibly(), 和tryAcquire() 方法, 如果计数器的值小于许可值,那么线程就会被阻塞直到计数器到达或者大于许可值。
http://ifeve.com/thread-synchronization-utilities-2/ http://ifeve.com/thread-synchronization-utilities-3/
线程同步工具(三)等待多个并发事件完成
CountDownLatch的使用,当CountDownLatch.await()会阻塞到CountDownLatch为0时。 常用来等待多个线程结束
http://ifeve.com/thread-synchronization-utilities-4/
线程同步工具(四)在同一个点同步任务
CyclicBarrier 的使用,跟CountDownLatch同步类似,不过CyclicBarrier 是周期的同步。 例如需要5个消费者各自生产一个单元的数据,将这5个资源打包之后进行处理,就可以使用这个类进行5个资源的循环同步。
http://ifeve.com/thread-synchronization-utilities-5/ PS:原文的例子没有体现出"循环"这个特性。网上的例子大多也是CountDownLatch功能的一个翻版,大多是用多个线程同时开始来作为例子的,这个完全没有体现出"循环"这个特性,也无法和CountDownLatch作出明显的区分。 下面给出自己的一个例子
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* Created by whl on 2017/3/7.
*/
class Producer extends Thread {
private CyclicBarrier cyclicBarrier;
public Producer(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) { //生产者生产0 到 99的数据
System.out.println(i);
try {
cyclicBarrier.await(); // 等待其他生产者,从而生产同一批次的数据,这个是循环的,不需要重置状态
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
public class Main2 {
private static final int threadCount = 5; // 五个生产者线程
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(threadCount, new Runnable() {
public void run() {
System.out.println("======== Bound ======="); // 模拟同批操作,即所有的1打个包,所有的2打个包
}
});
for (int i = 0; i < threadCount; i++) {
new Producer(cyclicBarrier).start(); // 启动所有生产者
}
}
}
运行结果:
0
0
0
0
0
======== Bound =======
1
1
1
1
1
======== Bound =======
2
2
2
2
2
... //中间数据省略
======== Bound =======
98
98
98
98
98
======== Bound =======
99
99
99
99
99
======== Bound =======
可以看到同一批次的数据被包含在同一个Bound中进行处理,这个在多个生产者生产有序数据,并且消费者又要按批次消费的时候有用
线程同步工具(五)运行阶段性并发任务
Phaser翻译成阶段,他是比CountDownLatch,CyclicBarrier 功能上更加强大,并且有一部分功能上的重叠。 通过Phaser在线程的每个阶段进行同步
Phaser有一点强大的地方在于,参与各阶段同步的线程数量是可以动态调整的,什么意思呢? 借用原文的例子,假如现在有3个线程,每个线程要完成三个阶段的同步任务
- 在指定的文件夹和子文件夹中获得文件扩展名为.log的文件列表。
- 过滤第一步的列表中修改超过24小时的文件。
- 在操控台打印结果。 一般的,所有的3个线程会完整的运行完这3个阶段的工作,但是,假如有一个线程运行在第一个阶段《在指定的文件夹和子文件夹中获得文件扩展名为.log的文件列表》而未找到log文件列表,那么阶段2和阶段3的任务是不需要执行下去了,也就是需要同步的线程从3个减少到了2个,那么可以调用Phaser.arriveAndDeregister()进行对该线程取消同步。
详细的操作逻辑参考原文例子,描述的比较清楚了
参考 http://ifeve.com/thread-synchronization-utilities-6-2/(原文) http://reeboo.iteye.com/blog/2035392 http://www.jasongj.com/java/thread_communication/(对比清晰)
线程同步工具(六)控制并发阶段性任务的改变
可以对Phaser的每个阶段进行终止
Phaser 类提供每次phaser改变阶段都会执行的方法。它是 onAdvance() 方法。它接收2个参数:当前阶段数和注册的参与者数;它返回 Boolean 值,如果phaser继续它的执行,则为 false;否则为真,即phaser结束运行并进入 termination 状态。
如果注册参与者为0,此方法的默认的实现值为真,要不然就是false。如果你扩展Phaser类并覆盖此方法,那么你可以修改它的行为。通常,当你要从一个phase到另一个,来执行一些行动时,你会对这么做感兴趣的。
http://ifeve.com/thread-synchronization-utilities-7/
线程同步工具(七)在并发任务间交换数据
Exchanger可以在两个线程之间交换数据,只能是2个线程,他不支持更多的线程之间互换数据。
当线程A调用Exchange对象的exchange()方法后,他会陷入阻塞状态,直到线程B也调用了exchange()方法,然后以线程安全的方式交换数据,之后线程A和B继续运行
http://ifeve.com/thread-synchronization-utilities-8/ http://blog.csdn.net/andycpp/article/details/8854593
第四章: 线程执行者
第四章 线程执行者(一)引言
通常,当你在Java中开发一个简单的并发编程应用程序,你会创建一些Runnable对象并创建相应的Thread对象来运行它们。如果你开发一个运行多个并发任务的程序,这种途径的缺点如下:
你必须要实现很多相关代码来管理Thread对象(创建,结束,获得的结果)。 你必须给每个任务创建一个Thread对象。如果你执行一个大数据量的任务,那么这可能影响应用程序的吞吐量。 你必须有效地控制和管理计算机资源。如果你创建太多线程,会使系统饱和。 为了解决以上问题,从Java5开始JDK并发API提供一种机制。这个机制被称为Executor framework,接口核心是Executor,Executor的子接口是ExecutorService,而ThreadPoolExecutor类则实现了这两个接口。 这个机制将任务的创建与执行分离。使用执行者,你只要实现Runnable对象并将它们提交给执行者。执行者负责执行,实例化和运行这些线程。除了这些,它还可以使用线程池提高了性能。当你提交一个任务给这个执行者,它试图使用线程池中的线程来执行任务,从而避免继续创建线程。
Callable接口是Executor framework的另一个重要优点。它跟Runnable接口很相似,但它提供了两种改进,如下:
这个接口中主要的方法叫call(),可以返回结果。 当你提交Callable对象到执行者,你可以获取一个实现Future接口的对象,你可以用这个对象来控制Callable对象的状态和结果。
线程执行者(二)创建一个线程执行者
Executors.newCachedThreadPool();的使用方法
ThreadPoolExecutor类也提供其他与结束执行者相关的方法,这些方法是:
shutdownNow():此方法立即关闭执行者。它不会执行待处理的任务,但是它会返回待处理任务的列表。当你调用这个方法时,正在运行的任务继续它们的执行,但这个方法并不会等待它们的结束。 isTerminated():如果你已经调用shutdown()或shutdownNow()方法,并且执行者完成关闭它的处理时,此方法返回true。 isShutdown():如果你在执行者中调用shutdown()方法,此方法返回true。 awaitTermination(long timeout, TimeUnit unit):此方法阻塞调用线程,直到执行者的任务结束或超时。TimeUnit类是个枚举类,有如下常 量:DAYS,HOURS,MICROSECONDS, MILLISECONDS, MINUTES,,NANOSECONDS 和SECONDS。
http://ifeve.com/thread-executors-2/
线程执行者(三)创建一个大小固定的线程执行者
与上newCachedThreadPool类似,用Executors.newFixedThreadPool(5);创建一个固定大小的线程池
http://ifeve.com/thread-executors-3/
线程执行者(四)执行者执行返回结果的任务
callable的使用方法,与runnable相似,只是有个返回值
Future<Integer> result=executor.submit(calculator); //获得calculator这个callable的Future对象 result.isDone() //判断是否运行完成 result.get() //获取返回值
http://ifeve.com/thread-executors-4/
线程执行者(五)运行多个任务并处理第一个结果
// 同时运行taskList的所有任务,返回第一个完成的任务的结果 // 通常用来验证不同实现之间的时间效率 result = executor.invokeAny(taskList);
所以,我们有两个任务,可以返回true值或抛出异常。有以下4种情况:
两个任务都返回ture。invokeAny()方法的结果是第一个完成任务的名称。 第一个任务返回true,第二个任务抛出异常。invokeAny()方法的结果是第一个任务的名称。 第一个任务抛出异常,第二个任务返回true。invokeAny()方法的结果是第二个任务的名称。 两个任务都抛出异常。在本例中,invokeAny()方法抛出一个ExecutionException异常。
http://ifeve.com/thread-executors-5/
线程执行者(六)运行多个任务并处理所有结果
这一章其实就讲了resultList=executor.invokeAll(taskList);的使用
http://ifeve.com/thread-executors-6/
线程执行者(七)执行者延迟运行一个任务
这一章讲了 ScheduledThreadPoolExecutor executor=(ScheduledThreadPoolExecutor)Executors.newScheduledThreadPool(1); // 创建一个ScheduledThreadPoolExecutor executor.schedule(task,i+1 , TimeUnit.SECONDS); // 隔一定的延迟运行线程
你必须使用schedule()方法,让执行者在一段时间后执行任务。这个方法接收3个参数,如下:
你想要执行的任务 你想要让任务在执行前等待多长时间 时间单位,指定为TimeUnit类的常数
http://ifeve.com/thread-executors-7/
线程执行者(八)执行者周期性地运行一个任务
ScheduledExecutorService executor=Executors.newScheduledThreadPool(1); // 创建一个ScheduledThreadPoolExecutor ScheduledFuture<?> result=executor.scheduleAtFixedRate(task,1, 2, TimeUnit.SECONDS); // 周期性的运行线程 你已经使用了scheduledAtFixedRate()方法。
此方法接收4个参数:
你想要周期性执行的任务、 第一次执行任务的延迟时间、 两次执行之间的间隔期间、 第2、3个参数的时间单位。
http://ifeve.com/thread-executors-8/
线程执行者(九)执行者取消一个任务
这一章其实是讲了Future.cancel()方法
当你想要取消你已提交给执行者的任务,使用Future接口的cancel()方法。根据cancel()方法参数和任务的状态不同,这个方法的行为将不同:
如果这个任务已经完成或之前的已被取消或由于其他原因不能被取消,那么这个方法将会返回false并且这个任务不会被取消。 如果这个任务正在等待执行者获取执行它的线程,那么这个任务将被取消而且不会开始它的执行。 如果这个任务已经正在运行,则视方法的参数情况而定。 cancel()方法接收一个Boolean值参数。如果参数为true并且任务正在运行,那么这个任务将被取消。如果参数为false并且任务正在运行,那么这个任务将不会被取消。
线程执行者(十)执行者控制一个任务完成
这一章讲了在线程结束之后可以自动调用一个方法,用来做一些清理操作
public class ResultTask extends FutureTask<String> {
@Override
protected void done() { // 线程完成之后会被调用
if (isCancelled()) {
System.out.printf("%s: Has been canceled\n",name);
} else {
System.out.printf("%s: Has finished\n",name);
}
}
}
ExecutableTask executableTask=new ExecutableTask("Task "+i);
resultTasks[i]=new ResultTask(executableTask);
executor.submit(resultTasks[i]);
http://ifeve.com/thread-executors-10/
线程执行者(十一)执行者分离任务的启动和结果的处理
这一章讲了CompletionService的使用
在Java5的多线程中,可以使用Callable接口来实现具有返回值的线程。使用线程池的submit方法提交Callable任务,利用submit方法返回的Future存根,调用此存根的get方法来获取整个线程池中所有任务的运行结果。 方法一:如果是自己写代码,应该是自己维护一个Collection保存submit方法返回的Future存根,然后在主线程中遍历这个Collection并调用Future存根的get()方法取到线程的返回值。 方法二:使用CompletionService类,它整合了Executor和BlockingQueue的功能。你可以将Callable任务提交给它去执行,然后使用类似于队列中的take方法获取线程的返回值。
使用方法一,自己创建一个集合来保存Future存根并循环调用其返回结果的时候,主线程并不能保证首先获得的是最先完成任务的线程返回值。它只是按加入线程池的顺序返回。因为take方法是阻塞方法,后面的任务完成了,前面的任务却没有完成,主程序就那样等待在那儿,只到前面的完成了,它才知道原来后面的也完成了。 使用方法二,使用CompletionService来维护处理线程不的返回结果时,主线程总是能够拿到最先完成的任务的返回值,而不管它们加入线程池的顺序。
http://ifeve.com/thread-executors-11/(原文) http://www.cnblogs.com/nayitian/p/3273468.html(更清晰的解释)
线程执行者(十二)执行者控制被拒绝的任务
当你想要结束执行者的执行,你使用shutdown()方法来表明它的结束。执行者等待正在运行或等待它的执行的任务的结束,然后结束它们的执行。 如果你在shutdown()方法和执行者结束之间,提交任务给执行者,这个任务将被拒绝,因为执行者不再接收新的任务。ThreadPoolExecutor类提供一种机制,在调用shutdown()后,不接受新的任务。
http://ifeve.com/thread-executors-12/
第五章: Fork/Join 框架
Fork/Join框架(一)引言
Fork/Join框架(二)创建一个Fork/Join池
ForkJoinPool pool=new ForkJoinPool(); //创建一个ForkJoinPool
public class Task extends RecursiveAction {// 实现一个RecursiveAction
@Override
protected void compute() { //重写compute方法
if (last-first<10) { // 这里就是类似递归的调用了
updatePrices();
} else {
int middle=(last+first)/2;
System.out.printf("Task: Pending tasks:
%s\n",getQueuedTaskCount());
Task t1=new Task(products, first,middle+1, increment);
Task t2=new Task(products, middle+1,last, increment);
invokeAll(t1, t2); //这个相当于t1.fork,t2.fork
}
}
http://ifeve.com/fork-join-2/ http://blog.csdn.net/ouyang_peng/article/details/46491217
Fork/Join框架(三)加入任务的结果
跟上面的例子相似,只是RecursiveTask这个相对于RecursiveAction多了返回结果 通过 RecursiveTask.get()或者RecursiveTask.join()方法获取返回值
http://ifeve.com/fork-join-3/ http://blog.csdn.net/ouyang_peng/article/details/46491217
Fork/Join框架(四)异步运行任务
Fork/Join框架(五)在任务中抛出异常
Fork/Join框架(六)取消任务
第六章: 并发集合
这一章略,主要是一些并发集合的使用。
并发集合(一)引言
Java提供了你可以在你的并发程序中使用的,而且不会有任何问题或不一致的数据集合。基本上,Java提供两种在并发应用程序中使用的集合:
阻塞集合:这种集合包括添加和删除数据的操作。如果操作不能立即进行,是因为集合已满或者为空,该程序将被阻塞,直到操作可以进行。 非阻塞集合:这种集合也包括添加和删除数据的操作。如果操作不能立即进行,这个操作将返回null值或抛出异常,但该线程将不会阻塞。 通过本章的指南,你将学习如何使用一些可以用在并发应用程序中的Java集合。这包括:
非阻塞列表,使用ConcurrentLinkedDeque类。 阻塞列表,使用LinkedBlockingDeque类。 用在生产者与消费者数据的阻塞列表,使用LinkedTransferQueue类。 使用优先级排序元素的阻塞列表,使用PriorityBlockingQueue类。 存储延迟元素的阻塞列表,使用DelayQueue类。 非阻塞可导航的map,使用ConcurrentSkipListMap类。 随机数,使用ThreadLocalRandom类 原子变量,使用AtomicLong和AtomicIntegerArray类
http://ifeve.com/concurrent-collections-1/
并发集合(二)使用非阻塞线程安全的列表
ConcurrentLinkedDeque 的使用 http://ifeve.com/concurrent-collections-2/
并发集合(三)使用阻塞线程安全的列表
LinkedBlockingDeque 的使用 http://ifeve.com/concurrent-collections-3/
并发集合(四)用优先级对使用阻塞线程安全的列表排序
PriorityBlockingQueue 的使用 http://ifeve.com/concurrent-collections-4/
并发集合(五)使用线程安全的、带有延迟元素的列表
DelayedQueue 的使用 http://ifeve.com/concurrent-collections-5/
并发集合(六)使用线程安全的NavigableMap
ConcurrentSkipListMap 的使用 http://ifeve.com/concurrent-collections-6/
并发集合(七)创建并发随机数
ThreadLocalRandom 的使用 http://ifeve.com/concurrent-collections-7/
并发集合(八)使用原子变量
AtomicLong 等原子变量的使用 http://ifeve.com/concurrent-collections-8/
并发集合(九)使用原子 arrays
AtomicIntegerArray 的使用 http://ifeve.com/concurrent-collections-9/
第七章: 定制并发类
这一章就略,定制并发类,使用的不多
定制并发类(一)引言
http://ifeve.com/customizing-concurrency-classes-1/
定制并发类(二)定制ThreadPoolExecutor类
继承重写ThreadPoolExecutor,重写一些方法 http://ifeve.com/customizing-concurrency-classes-2/
定制并发类(三)实现一个基于优先级的Executor类
一个基于优先队列实现的,重写的Executor类的例子 http://ifeve.com/customizing-concurrency-classes-3/
定制并发类(四)实现ThreadFactory接口生成自定义的线程
定制并发类(五)在一个Executor对象中使用我们的ThreadFactory
实现ThreadFactory 接口,自定义一个线程工厂类 http://ifeve.com/customizing-concurrency-classes-4/
使用自定义的ThreadFactory 实现 http://ifeve.com/customizing-concurrency-classes-5/
定制并发类(六)自定义在计划的线程池内运行的任务
继承ScheduledThreadPoolExecutor ,重写自定义 http://ifeve.com/customizing-concurrency-classes-7-2/
定制并发类(七)实现ThreadFactory接口生成自定义的线程给Fork/Join框架
自定义的ThreadFactory与Fork/Join框架的结合的例子
http://ifeve.com/customizing-concurrency-classes-7/
定制并发类(八)自定义在 Fork/Join 框架中运行的任务
自定义的ForkJoinTask,重写自定义 http://ifeve.com/customizing-concurrency-classes-9-2/
定制并发类(九)实现一个自定义的Lock类
实现Lock接口,自定义锁 http://ifeve.com/customizing-concurrency-classes-9/
定制并发类(十)实现一个基于优先级的传输队列
http://ifeve.com/customizing-concurrency-classes-11-2/
定制并发类(十一)实现自定义的原子对象
继承AtomicInteger原子,扩展功能
第八章: 测试并发应用程序
第八章- 测试并发应用(引言)
http://ifeve.com/testing-concurrent-applications-1/
测试并发应用 (一)监控Lock接口
可以查看锁的状态,正在等待锁的线程,当前锁的拥有者 由于ReentrantLock 的等待锁的线程,和当前锁的拥有者的方法是protected的,所以需要继承自ReentrantLock ,把这两个方法暴露出来 这两个方法是getQueuedThreads()和getOwner()
getOwnerName():只有唯一一个线程可以执行被Lock对象保护的临界区。锁存储了正在执行临界区的线程。此线程会被ReentrantLock类的保护方法 getOwner()返回。 此方法使用 getOwner() 方法来返回线程的名字。 getThreads():当线程正在执行临界区时,其他线程尝试进入临界区就会被放到休眠状态一直到他们可以继续执行为止。ReentrantLock类保护方法getQueuedThreads() 返回 正在等待执行临界区的线程list。此方法返回 getQueuedThreads() 方法返回的结果。 我们还使用了 ReentrantLock 类里实现的其他方法:
hasQueuedThreads():此方法返回 Boolean 值表明是否有线程在等待获取此锁 getQueueLength(): 此方法返回等待获取此锁的线程数量 isLocked(): 此方法返回 Boolean 值表明此锁是否为某个线程所拥有 isFair(): 此方法返回 Boolean 值表明锁的 fair 模式是否被激活
ReentrantLock 类还有其他方法也是用来获取Lock对象的信息的:
getHoldCount(): 返回当前线程获取锁的次数 isHeldByCurrentThread(): 返回 Boolean 值,表明锁是否为当前线程所拥有
http://ifeve.com/testing-concurrent-applications-2/
测试并发应用(二)监控Phaser类
获取Phaser的状态 任务并打印关于phaser对象的状态信息到操控台。 我们使用以下的方法来获取phaser对象的状态:
getPhase():此方法返回phaser 任务的 actual phase getRegisteredParties(): 此方法返回使用phaser对象作为同步机制的任务数 getArrivedParties(): 此方法返回已经到达actual phase末端的任务数 getUnarrivedParties(): 此方法返回还没到达actual phase末端的任务数
http://ifeve.com/testing-concurrent-applications-3/
测试并发应用(三)监控Executor框架
获取Executor的状态 在这个指南里,你实现了一个任务,它对它的执行线程进行了一段随机毫秒数的阻塞。然后,你发送10个任务给执行者,并且当你正在等待它们的终结的同时,你已经把关于执行者的状态的信息写入到操控台。你使用了以下的方法来获取 Executor 对象的状态:
getCorePoolSize(): 此方法返回一个int数,表示线程的核心数。它是当执行者没有执行任何任务时,在内部线程池的线程数。 getPoolSize(): 此方法返回一个int数,表示内部线程池的真实大小。 getActiveCount(): 此方法返回一个int数,表示当前执行任务的线程数。 getTaskCount(): 此方法返回一个long数,表示已经分配执行的任务数。 getCompletedTaskCount(): 此方法返回一个long数,表示已经被这个执行者执行并结束执行的任务数。 isShutdown(): 当 执行的 shutdown() 方法被调用来结束执行时,此方法返回 Boolean 值。 isTerminating(): 当执行者正在操作shutdown(),但是还没结束时,此方法返回 Boolean 值。 isTerminated(): 当这个执行者结束执行时,此方法返回 Boolean 值。
http://ifeve.com/testing-concurrent-applications-4/
测试并发应用(四)监控Fork/Join池
获取ForkJoinPool 的状态 在这个指南,你实现了任务 使用 ForkJoinPool 类和一个扩展 RecursiveAction 类的 Task 类来增加array的元素;它是 ForkJoinPool 类执行的任务种类之一。当任务还在处理array时,你就把关于 ForkJoinPool 类的状态信息打印到操控台。 你使用了ForkJoinPool类中的以下方法:
getPoolSize(): 此方法返回 int 值,它是ForkJoinPool内部线程池的worker线程们的数量。 getParallelism(): 此方法返回池的并行的级别。 getActiveThreadCount(): 此方法返回当前执行任务的线程的数量。 getRunningThreadCount():此方法返回没有被任何同步机制阻塞的正在工作的线程。 getQueuedSubmissionCount(): 此方法返回已经提交给池还没有开始他们的执行的任务数。 getQueuedTaskCount(): 此方法返回已经提交给池已经开始他们的执行的任务数。 hasQueuedSubmissions(): 此方法返回 Boolean 值,表明这个池是否有queued任务还没有开始他们的执行。 getStealCount(): 此方法返回 long 值,worker 线程已经从另一个线程偷取到的时间数。 isTerminated(): 此方法返回 Boolean 值,表明 fork/join 池是否已经完成执行。
测试并发应用(五) 编写有效的日志
写日志的,这里有很多日志的实现方式,就不详细说了 http://ifeve.com/testing-concurrent-applications-6-2/
测试并发应用(六)用 FindBugs 分析并发代码
用findbugs分析的,findbugs有很多的版本,独立的,各种ide的插件的,maven插件的等等,不详细说了。当然除了findbugs之外还有一些可以使用的同类工具。 http://ifeve.com/testing-concurrent-applications-7/
测试并发应用(七)配置Eclipse来调试并发代码
配置eclipse查看各个线程的状态的,不详细说了。 http://ifeve.com/testing-concurrent-applications-8/
测试并发应用(八)配置NetBeans来调试并发代码
配置netbeans查看各个线程的状态的,不详细说了。 http://ifeve.com/testing-concurrent-applications-9/
测试并发应用(九)MultithreadedTC测试并发代码
MultithreadedTC 做测试 http://ifeve.com/testing-concurrent-applications-10/