自从写了大半点hibernate读书笔记被csdn的渣渣编辑器吞了之后,已经很多天没有再用博客来记录自己的学习了。这段时间深入学习了java并发这一块,收获良多,再次记录。
无状态的对象: 不包含任何域,也不包含任何对其他类中域的引用。计算过程的临时状态仅存在于线程栈上的局部变量中,并只能由正在执行的线程访问。
++count不是原子的, 包含3个独立的操作,读取count的值,把值+1,把计算结果写入count。
当某个计算的正确性取决于多个线程到交替执行顺序时,就会发生竞态条件。
最常见的竞态条件类型就是“先检查后执行”操作,即通过一个可能失效的观测结果来决定下一步的动作。
在计数器这个问题中可以使用原子类如AtomicLong或者AtomicReference
每个java对象都可以作为一个实现同步的锁,成为内置锁活着监视器锁。在进入同步代码块时自动获得锁,推出时自动释放锁。
内置锁是可重入的,意味着获取锁的操作的粒度是线程,而不是调用。 重入的一种实现方法为:为每个锁关联一个获取计数值和一个所有者线程。
之所以每个对象都有一个内置锁,只是为了免去显示地创建锁对象。
synchronized可以用来实现原子性、临界区、内存可见性。
关于可见性,可以看 http://blog.csdn.net/u012422829/article/details/46127827
不要在构造函数函数随便创建匿名类然后发布它们。
不在构造函数中随便起线程, 如果起要看有没有发布匿名类对象,不在构造函数内启动。
public class Holder{
private int n;
public Holder(int n){ this.n=n;}
public void test(){
if(n!=n){ throw new Exception("bug??");
}
}
Fail-Fast机制:
我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛ConcurrentModificationException,这就是所谓fail-fast策略。
这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。
在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map:
注意到modCount声明为volatile,保证线程之间修改的可见性。
在HashMap的API中指出:
由所有HashMap类的“collection 视图方法”所返回的迭代器都是快速失败的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来不确定的时间发生任意不确定行为的风险。
注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。
Fail-Safe机制:Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败(一般的集合类)的,而java.util.concurrent包下面的所有的类(比如CopyOnWriteArrayList,ConcurrentHashMap )都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。
闭锁可以用来确保某些活动直到其他活动完成后才继续执行。
CountDownLatch是一种灵活的闭锁实现,可以使一个或多个线程等待一组时间发生。闭锁状态包括一个计数器,这个计数器被初始化为一个正数,表示需要等待的事件数目。
countDown方法递减计数器,表示一个事件已经发生了。await方法等待计数器达到0,这表示所有需要等待的时间都已经发生。如果计数器的值非0,那么await会一直阻塞直到计数器为0,或者等待中的线程中断或等待超时。
FutureTask(它表示的计算是通过callable实现的)也可以用做闭锁。
Semaphore 可以用于实现资源池,比如数据库连接池。
Semaphore sem = new Semaphore();
sem.acquire()
...
sem.release()
类似于闭锁。栅栏能阻塞一组线程直到某个事件发生。他们的关键区别在于: 所有线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
CountDownLatch | CyclicBarrier |
减计数方式 | 加计数方式 |
计算为0时释放所有等待的线程 | 计数达到指定值时释放所有等待线程 |
计数为0时,无法重置 | 计数达到指定值时,计数置为0重新开始 |
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响 | 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞 |
不可重复利用 | 可重复利用 |
可以参考:
http://blog.csdn.net/tolcf/article/details/50925145
public class Memoizer implements Computable {
private final ConcurrentMap> cache
= new ConcurrentHashMap>();
private final Computable c;
public Memoizer(Computable c) {
this.c = c;
}
public V compute(final A arg) throws InterruptedException {
while (true) {
Future f = cache.get(arg);
if (f == null) {
Callable eval = new Callable() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
FutureTask ft = new FutureTask(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.按指定频率周期执行某个任务。
初始化延迟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;
}
|
invokeAll(Collection 执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。 |
|
|
invokeAll(Collection 执行给定的任务,当所有任务完成或超时期满时(无论哪个首先发生),返回保持任务状态和结果的 Future 列表。 |
|
|
invokeAny(Collection 执行给定的任务,如果某个任务已成功完成(也就是未抛出异常),则返回其结果。 |
如果你向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,它会在结果不可用时阻塞。
shutdown()新的任务不会再被提交到线程池,但之前的都会依旧执行,通过中断方式停止空闲的(根据没有获取锁来确定)线程。
shutdownNow()则向所有正在执行的线程发出中断信号以尝试终止线程,并将工作队列中的任务以列表方式的结果返回。
两者区别:
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种基本策略的实现。分别是:
下面分别详细说明。
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 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());
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
4
5
|
public
void
rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if
(!e.isShutdown()) {
r.run();
}
}
|
注意,和DiscardOldestPolicy同样,也会先判断ThreadPoolExecutor对象的状态,之后执行任务。这样处理的一个好处,是让caller线程运行任务,以推迟该线程进一步提交新任务,有效的缓解了线程池对象饱和的情况。
上面只是SunJDK中提供的4种最基本策略,开发者可以根据具体需求定制。
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 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());
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("执行任务完成~");
}
/**
* 运行结果:
* 执行任务之前~
我执行了一个任务~
执行任务之后~
执行任务完成~
*/
}
public boolean sendOnSharedLine(Stringmessage)
throws InterruptedException{
lock.lockInterruptibly();
try{
return cancellableSendOnSharedLine(message);
}finally{
lock.unlock();
}
}
private boolean cancellableSendOnSharedLine(String message)
throwsInterruptedException{...}
}
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之前。