自从写了大半点hibernate读书笔记被csdn的渣渣编辑器吞了之后,已经很多天没有再用博客来记录自己的学习了。这段时间深入学习了java并发这一块,收获良多,再次记录。
第二章 线程安全性
1.无状态的一定是线程安全的
无状态的对象: 不包含任何域,也不包含任何对其他类中域的引用。计算过程的临时状态仅存在于线程栈上的局部变量中,并只能由正在执行的线程访问。
2.原子性
++count不是原子的, 包含3个独立的操作,读取count的值,把值+1,把计算结果写入count。
3.竞态条件
当某个计算的正确性取决于多个线程到交替执行顺序时,就会发生竞态条件。
最常见的竞态条件类型就是“先检查后执行”操作,即通过一个可能失效的观测结果来决定下一步的动作。
在计数器这个问题中可以使用原子类如AtomicLong或者AtomicReference<BigInteger>来统计已处理请求的数量。
4.内置锁与可重入
每个java对象都可以作为一个实现同步的锁,成为内置锁活着监视器锁。在进入同步代码块时自动获得锁,推出时自动释放锁。
内置锁是可重入的,意味着获取锁的操作的粒度是线程,而不是调用。 重入的一种实现方法为:为每个锁关联一个获取计数值和一个所有者线程。
之所以每个对象都有一个内置锁,只是为了免去显示地创建锁对象。
第三章 对象的共享
1.synchronized的作用
synchronized可以用来实现原子性、临界区、内存可见性。
关于可见性,可以看 http://blog.csdn.net/u012422829/article/details/46127827
2.重排序
3.非原子的64位操作
4.volatile
5.this引用逸出
不要在构造函数函数随便创建匿名类然后发布它们。
不在构造函数中随便起线程, 如果起要看有没有发布匿名类对象,不在构造函数内启动。
6.ThreadLocal
7.不变性
8.安全发布的常用模式
public class Holder{
private int n;
public Holder(int n){ this.n=n;}
public void test(){
if(n!=n){ throw new Exception("bug??");
}
}
当调用test的时候,可能是会抛出异常的!
第四章 对象的组合
第五章 基础构建模块
1.快速失败和安全失败
Fail-Fast机制:
我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛ConcurrentModificationException,这就是所谓fail-fast策略。
这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。
- HashIterator() {
- expectedModCount = modCount;
- if (size > 0) { // advance to first entry
- Entry[] t = table;
- while (index < t.length && (next = t[index++]) == null)
- ;
- }
- }
在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map:
注意到modCount声明为volatile,保证线程之间修改的可见性。
- final Entry<K,V> nextEntry() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
在HashMap的API中指出:
由所有HashMap类的“collection 视图方法”所返回的迭代器都是快速失败的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来不确定的时间发生任意不确定行为的风险。
注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。
Fail-Safe机制:Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败(一般的集合类)的,而java.util.concurrent包下面的所有的类(比如CopyOnWriteArrayList,ConcurrentHashMap )都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。
2.双端队列与工作密取
3. 闭锁
闭锁可以用来确保某些活动直到其他活动完成后才继续执行。
CountDownLatch是一种灵活的闭锁实现,可以使一个或多个线程等待一组时间发生。闭锁状态包括一个计数器,这个计数器被初始化为一个正数,表示需要等待的事件数目。
countDown方法递减计数器,表示一个事件已经发生了。await方法等待计数器达到0,这表示所有需要等待的时间都已经发生。如果计数器的值非0,那么await会一直阻塞直到计数器为0,或者等待中的线程中断或等待超时。
FutureTask(它表示的计算是通过callable实现的)也可以用做闭锁。
4.信号量
Semaphore 可以用于实现资源池,比如数据库连接池。
Semaphore sem = new Semaphore();
sem.acquire()
...
sem.release()
5.栅栏
类似于闭锁。栅栏能阻塞一组线程直到某个事件发生。他们的关键区别在于: 所有线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
CountDownLatch | CyclicBarrier |
减计数方式 | 加计数方式 |
计算为0时释放所有等待的线程 | 计数达到指定值时释放所有等待线程 |
计数为0时,无法重置 | 计数达到指定值时,计数置为0重新开始 |
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响 | 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞 |
不可重复利用 | 可重复利用 |
可以参考:
http://blog.csdn.net/tolcf/article/details/50925145
6.构建高效的结果缓存
public class Memoizer <A, V> implements Computable<A, V> {
private final ConcurrentMap<A, Future<V>> cache
= new ConcurrentHashMap<A, Future<V>>();
private final Computable<A, V> c;
public Memoizer(Computable<A, V> c) {
this.c = c;
}
public V compute(final A arg) throws InterruptedException {
while (true) {
Future<V> f = cache.get(arg);
if (f == null) {
Callable<V> eval = new Callable<V>() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(eval);
f = cache.putIfAbsent(arg, ft);
if (f == null) {
f = ft;
ft.run();
}
}
try {
return f.get();
} catch (CancellationException e) {
cache.remove(arg, f);
} catch (ExecutionException e) {
throw LaunderThrowable.launderThrowable(e.getCause());
}
}
}
}
第六章 任务执行
1.线程池的好处以及默认配置线程池
2.延迟任务与定时任务
1.按指定频率周期执行某个任务。
初始化延迟0ms开始执行,每隔100ms重新执行一次任务。
/**
* 以固定周期频率执行任务
*/
public static void executeFixedRate() {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(
new EchoServer(),
0,
100,
TimeUnit.MILLISECONDS);
}
间隔指的是连续两次任务开始执行的间隔。
2.按指定频率间隔执行某个任务。
初始化时延时0ms开始执行,本次执行结束后延迟100ms开始下次执行。
/**
* 以固定延迟时间进行执行
* 本次任务执行完成后,需要延迟设定的延迟时间,才会执行新的任务
*/
public static void executeFixedDelay() {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleWithFixedDelay(
new EchoServer(),
0,
100,
TimeUnit.MILLISECONDS);
}
3.周期定时执行某个任务。
有时候我们希望一个任务被安排在凌晨3点(访问较少时)周期性的执行一个比较耗费资源的任务,可以使用下面方法设定每天在固定时间执行一次任务。
/**
* 每天晚上8点执行一次
* 每天定时安排任务进行执行
*/
public static void executeEightAtNightPerDay() {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
long oneDay = 24 * 60 * 60 * 1000;
long initDelay = getTimeMillis("20:00:00") - System.currentTimeMillis();
initDelay = initDelay > 0 ? initDelay : oneDay + initDelay;
executor.scheduleAtFixedRate(
new EchoServer(),
initDelay,
oneDay,
TimeUnit.MILLISECONDS);
}
/**
* 获取指定时间对应的毫秒数
* @param time "HH:mm:ss"
* @return
*/
private static long getTimeMillis(String time) {
try {
DateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
DateFormat dayFormat = new SimpleDateFormat("yy-MM-dd");
Date curDate = dateFormat.parse(dayFormat.format(new Date()) + " " + time);
return curDate.getTime();
} catch (ParseException e) {
e.printStackTrace();
}
return 0;
}
3.Future对象
4.为任务设置时限
| invokeAll(Collection<Callable<T>> tasks) 执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。 | |
| invokeAll(Collection<Callable<T>> tasks, long timeout, TimeUnit unit) 执行给定的任务,当所有任务完成或超时期满时(无论哪个首先发生),返回保持任务状态和结果的 Future 列表。 | |
| invokeAny(Collection<Callable<T>> tasks) 执行给定的任务,如果某个任务已成功完成(也就是未抛出异常),则返回其结果。 |
5.使用CompletionService批处理任务
如果你向Executor提交了一个批处理任务,并且希望在它们完成后获得结果。为此你可以保存与每个任务相关联的Future,然后不断地调用timeout为零的get,来检验Future是否完成。这样做固然可以,但却相当乏味。幸运的是,还有一个更好的方法:完成服务(Completion service)。
CompletionService整合了Executor和BlockingQueue的功能。你可以将Callable任务提交给它去执行,然后使用类似于队列中的take和poll方法,在结果完整可用时获得这个结果,像一个打包的Future。ExecutorCompletionService是实现CompletionService接口的一个类,并将计算任务委托给一个Executor。
ExecutorCompletionService的实现相当直观。它在构造函数中创建一个BlockingQueue,用它去保持完成的结果。计算完成时会调用FutureTask中的done方法。当提交一个任务后,首先把这个任务包装为一个QueueingFuture,它是FutureTask的一个子类,然后覆写done方法,将结果置入BlockingQueue中,take和poll方法委托给了BlockingQueue,它会在结果不可用时阻塞。
- //使用CompletionService(完成服务)保持Executor处理的结果
- public void count2() throws InterruptedException, ExecutionException{
- ExecutorService exec = Executors.newCachedThreadPool();
- CompletionService<Integer> execcomp = new ExecutorCompletionService<Integer>(exec);
- for(int i=0; i<10; i++){
- execcomp.submit(getTask());
- }
- int sum = 0;
- for(int i=0; i<10; i++){
- //检索并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。
- Future<Integer> future = execcomp.take();
- sum += future.get();
- }
- System.out.println("总数为:"+sum);
- exec.shutdown();
- }
第七章 取消与关闭
1.interrupt机制的优点
2.对中断的理解
3.shutdown()和shutdownNow()的区别
shutdown()新的任务不会再被提交到线程池,但之前的都会依旧执行,通过中断方式停止空闲的(根据没有获取锁来确定)线程。
shutdownNow()则向所有正在执行的线程发出中断信号以尝试终止线程,并将工作队列中的任务以列表方式的结果返回。
两者区别:
- 是一个要将线程池推到SHUTDOWN状态,一个将推到STOP状态
- 并且对运行的线程处理方式不同,shutdown()只中断空闲线程,而shutdownNow()会尝试中断所有活动线程
- 还有就是对队列中的任务处理,shutdown()队列中已有任务会继续执行,而shutdownNow()会直接取出不被执行
4.守护线程
第八章 线程池的使用
1.线程池
2.固定大小的线程池仍能耗尽资源
3.SynchronousQueue的适用场合
4.饱和策略
RejectedExecutionHandler接口
当ThreadPoolExecutor执行任务的时候,如果线程池的线程已经饱和,并且任务队列也已满。那么就会做丢弃处理,这也是execute()方法实现中的操作,源码如下:
1
2
|
else
if
(!addWorker(command,
false
))
reject(command);
|
这个reject()方法很简单,直接调用丢弃处理的handler方法的rejectedExecution()。
在java.util.concurrent中,专门为此定义了一个接口,是RejectedExecutionHandler:
1
2
3
|
public
interface
RejectedExecutionHandler {
void
rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
|
其中只有rejectedExecution()一个方法。返回为void,而参数一个是具体的Runnable任务,另一个则是被提交任务的ThreadPoolExecutor。
凡是实现了这个方法的类都可以作为丢弃处理器在ThreadPoolExecutor对象构造的时候作为参数传入,这个前面的文章已经提到过了。其中ThreadPoolExecutor给出了4种基本策略的实现。分别是:
- CallerRunsPolicy
- AbortPolicy
- DiscardPolicy
- DiscardOldestPolicy
下面分别详细说明。
1. 直接丢弃
这个也是实现最简单的类,其中的rejectedExecution()方法是空实现,即什么也不做,那么提交的任务将会被丢弃,而不做任何处理。
1
2
3
4
5
6
|
public
static
class
DiscardPolicy
implements
RejectedExecutionHandler {
public
DiscardPolicy() { }
public
void
rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
|
这个策略使用的时候要小心,要明确需求。不然不知不觉的任务就丢了。
2. 丢弃最老
和上面的有些类似,也是会丢弃掉一个任务,但是是队列中最早的。
实现如下:
1
2
3
4
5
6
7
8
9
10
|
public
static
class
DiscardOldestPolicy
implements
RejectedExecutionHandler {
public
DiscardOldestPolicy() { }
public
void
rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if
(!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
|
注意,会先判断ThreadPoolExecutor对象是否已经进入SHUTDOWN以后的状态。之后取出队列头的任务并不做任何处理,即丢弃,再重新调用execute()方法提交新任务。
3. 废弃终止
这个RejectedExecutionHandler类和直接丢弃不同的是,不是默默地处理,而是抛出java.util.concurrent.RejectedExecutionException异常,这个异常是RuntimeException的子类。这个策略实现如下:
1
2
3
4
5
6
7
8
9
|
public
static
class
AbortPolicy
implements
RejectedExecutionHandler {
public
AbortPolicy() { }
public
void
rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw
new
RejectedExecutionException(
"Task "
+ r.toString() +
" rejected from "
+
e.toString());
}
}
|
注意,处理这个异常的线程是执行execute()的调用者线程。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
//扩展线程池以提供日志和计时功能
public class TimingThreadPool extends ThreadPoolExecutor{
//需要重写配置型的构造方法
public TimingThreadPool(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public static void main(String[] args) {
//默认使用Executors.newCachedThreadPool()的配置方法,过期时间为60秒
TimingThreadPool pool = new TimingThreadPool(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
pool.runTask();
pool.shutdown();
}
//执行任务
public void runTask(){
this.execute(new Runnable(){
@Override
public void run() {
System.out.println("我执行了一个任务~");
}}
);
}
//执行任务之前
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
System.out.println("执行任务之前~");
}
//执行任务之后
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
System.out.println("执行任务之后~");
}
//执行任务完成,需要执行关闭操作才会调用这个方法
@Override
protected void terminated() {
super.terminated();
System.out.println("执行任务完成~");
}
/**
* 运行结果:
* 执行任务之前~
我执行了一个任务~
执行任务之后~
执行任务完成~
*/
}
4. 调用者执行策略
在这个策略实现中,任务还是会被执行,但线程池中不会开辟新线程,而是提交任务的线程来负责维护任务。
1
2
3
4
5
|
public
void
rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if
(!e.isShutdown()) {
r.run();
}
}
|
注意,和DiscardOldestPolicy同样,也会先判断ThreadPoolExecutor对象的状态,之后执行任务。这样处理的一个好处,是让caller线程运行任务,以推迟该线程进一步提交新任务,有效的缓解了线程池对象饱和的情况。
上面只是SunJDK中提供的4种最基本策略,开发者可以根据具体需求定制。
5.拓展executor
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
//扩展线程池以提供日志和计时功能
public class TimingThreadPool extends ThreadPoolExecutor{
//需要重写配置型的构造方法
public TimingThreadPool(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public static void main(String[] args) {
//默认使用Executors.newCachedThreadPool()的配置方法,过期时间为60秒
TimingThreadPool pool = new TimingThreadPool(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
pool.runTask();
pool.shutdown();
}
//执行任务
public void runTask(){
this.execute(new Runnable(){
@Override
public void run() {
System.out.println("我执行了一个任务~");
}}
);
}
//执行任务之前
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
System.out.println("执行任务之前~");
}
//执行任务之后
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
System.out.println("执行任务之后~");
}
//执行任务完成,需要执行关闭操作才会调用这个方法
@Override
protected void terminated() {
super.terminated();
System.out.println("执行任务完成~");
}
/**
* 运行结果:
* 执行任务之前~
我执行了一个任务~
执行任务之后~
执行任务完成~
*/
}
第十章 避免活跃性危险
1.转账问题中避免死锁
2.开放调用
3.死锁的避免和诊断
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
4.饥饿、活锁
第十一章 性能与可伸缩性
1.可伸缩性
2.线程引入的开销
3.减少锁的竞争
4.不要使用对象池来优化性能
第十三章 显式锁
1.轮询锁 定时锁
2.可中断的锁获取操作
public boolean sendOnSharedLine(Stringmessage)
throws InterruptedException{
lock.lockInterruptibly();
try{
return cancellableSendOnSharedLine(message);
}finally{
lock.unlock();
}
}
private boolean cancellableSendOnSharedLine(String message)
throwsInterruptedException{...}
}
3.lock是否公平
4.何时使用Reetrantlock而不是synchronized
第十五章 原子变量与非阻塞同步
1.CAS
2.非阻塞算法
3.ABA问题
第十六章 Java内存模型
1.重排序
2.Java内存模型,Happens-Before规则
Happens-before法则
Java的内存结构如下
如果多线程之间不共享数据,这也表现得很好,但是如果多线程之间要共享数据,那么这些乱序执行,数据在寄存器中这些行为将导致程序行为的不确定性,现在处理器已经是多核时代了,这些问题将会更加严重,每个线程都有自己的工作内存,多个线程共享主内存,如图
如果共享数据,什么时候同步到主内存让别人的线程读取数据呢?这又是不确定的,如果非要一致,那么代价高昂,这将牺牲处理器的性能,所以现在的处理器会牺牲存储一致性来换取性能,如果程序要确保共享数据的时候获得一致性,处理器通常了提供了一些关卡指令,这个可以帮助程序员来实现,但是各种处理器都不一样,如果要使程序能够跨平台是不可能的,怎么办?
使用Java,由JMM(Java Memeory Model Action)来屏蔽,我们只要和JMM的规定来使用一致性保证就搞定了,那么JMM又提供了什么保证呢?JMM的定义是通过动作的形式来描述的,所谓动作,包括变量的读和写,监视器加锁和释放锁,线程的启动和拼接,这就是传说中的happen before,要想A动作看到B动作的结果,B和A必须满足happen before关系,happen before法则如下:
程序次序规则:在一个线程内,按照程序代码的顺序,书写在前面的操作先行发生与书写在后面的操作。
冠程锁定规则:一个锁的unlock操作先行发生于“后面”对同一个锁的lock操作。这里的“后面”是指时间上的先后顺序。
volatile变量规则:对一个volatile变量的写操作先行发生于“后面”对这个变量的读操作。这里的“后面”同样是指时间上的先后顺序。
线程的启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
线程中断规则:对线程的interrupt()方法的调用先行发生于被中断线程的代码检测
对象终结原则:一个对象的初始化完成(构造函数执行完成)先行发生与它的finalize()方法的开始。
传递性:A发生在B之前,B发生在C之前,A一定发生在C之前。
3.安全初始化,不要用双检锁
- private static Resource resource=new Resource();
- public static Resource getInstance() {
- return resource;
- }
- private static class ResourceHolder{
- public static Resource resource=new Resource();
- }
- public static Resource getInstance() {
- return ResourceHolder.resource;
- }