记录一些阅读过程中我认为需要记录的地方。
例如:
Servlet的某个实现类里的每个自定义方法是非共享状态。但是该类如果引入了一个类变量,那么该实现类处于共享状态。
(注意,Servlet的实例只有一份,来一个请求会有一个线程去执行对应Servlet实例的service方法)
问题就在于check完之后,act之前,观察结果已经无效(被篡改)引发了非预期的异常。
例如经典的惰性初始化(单例模式)的错误写法:
if(instance == null) {
instance = new Object
}
return instance
例如i++
注意,即使该变量是线程安全的,复合操作依旧会带来线程安全问题。例如vector的put-if-absent
Java内置的互斥锁。注意,其机制是per-thread
,不是per-invocation
,这就意味着该内置锁是可以重入的。
经典的put-if-absent问题,例如我们使用Vector,虽然说其内部实现是将一系列方法用synchronized
加锁,但是如果你想要实现如下逻辑:如果不存在某个元素,那么增加进去,你可能会这样实现:
if(!vector.contains('X'))
vector.add('X')
这就是上文提到的复合操作,是存在同步问题的,我们需要在整个复合操作上加锁,也就是客户端加锁。
synchronized(vector) {
if(!vector.contains('X'))
vector.add('X')
}
而我们的例如ConcurrentHashMap
原生就提供类似方法。
接下来提供一个缓存实现的经典例子:
定义一个接口:
public interface Computable {
V compute(A arg);
}
假设,所有该接口的实现的compute
方法都需要耗费大量时间,我们需要缓存该结果,需要设计一个缓存类。类似于如下:
public class CacheResult2 implements Computable {
private Map cache = new HashMap<>();
private Computable computable;
public CacheResult2(Computable computable) {
this.computable = computable;
}
@Override
public V compute(A arg) {
V result = cache.get(arg);
if (result == null) {
result = cache.put(arg, computable.compute(arg));
}
return result;
}
}
我们使用一个HashMap
缓存结果,显然存在并发问题。接着,我们可能会对该方法加上synchronized
关键字,那么可能会出现一种情况:假如此时来了三个线程,t1,t2,t3,t1想计算arg是1的某个值,t2想计算arg为2的某个值,t3和t1一样,计算arg是1的某个值,那么有一种极端情况是t1,t2,t3分别拿到锁,这样对于t3花费的时间可能还没有不做缓存的时间还久。换一种思路。我们使用线程安全的集合类,这种方式同样存在问题:
如果某种计算十分耗费时间,那么很可能有多个线程同时在进行这个计算(注意是同一个arg)操作,那么效率同样很低。
最终的解决办法就是使用Future,当某个线程发现某个arg的结果正在计算中,那么他不会再进行计算。还有一个注意点是请使用map的putIfAbsent
方法,确保原子性,否则同样可能出现多次计算的问题,尽管这种可能性相对上一种情况会很小很小。给出代码:
public class CacheResult implements Computable {
private Map> cache = new ConcurrentHashMap<>();
private Computable computable;
public CacheResult(Computable computable) {
this.computable = computable;
}
@Override
public V compute(A arg) {
while (true) {
Future future = cache.get(arg);
if (future == null) {
Callable callable = () -> computable.compute(arg);
FutureTask futureTask = new FutureTask<>(callable);
future = cache.putIfAbsent(arg, futureTask); //原子性
if (future == null) { //保证只可能有一个线程进行计算动作
future = futureTask;
futureTask.run();
}
}
try {
return future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
cache.remove(arg);
}
}
}
}
此动作并不会中断某个线程,而是将该线程设置一个中断状态。由对应线程来决定是否要停止。
当你的代码调用了一个需要抛出InterruptedException
的方法时,
通常有2种做法:
传递给上层。或者调用interrupt恢复中断状态。(如果第一处catch不想对异常做处理,给后面的catch处理,那就需要重新intereupt当前线程)(当捕捉了InterruptedException,中断状态清除):
try {
} catch(InterruptedException e) {
Thread.currentThread.interrupt();//恢复中断状态
}
注意,调用某个线程的Interrupt方法,在不同上下文中是有不同的效果的,参见JDK注释:
If this thread is blocked in an invocation of the {@link
* Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
* Object#wait(long, int) wait(long, int)} methods of the {@link Object}
* class, or of the {@link #join()}, {@link #join(long)}, {@link
* #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
* methods of this class, then its interrupt status will be cleared and it
* will receive an {@link InterruptedException}.
*
*
If this thread is blocked in an I/O operation upon an {@link
* java.nio.channels.InterruptibleChannel InterruptibleChannel}
* then the channel will be closed, the thread's interrupt
* status will be set, and the thread will receive a {@link
* java.nio.channels.ClosedByInterruptException}.
*
*
If this thread is blocked in a {@link java.nio.channels.Selector}
* then the thread's interrupt status will be set and it will return
* immediately from the selection operation, possibly with a non-zero
* value, just as if the selector's {@link
* java.nio.channels.Selector#wakeup wakeup} method were invoked.
*
*
If none of the previous conditions hold then this thread's interrupt
* status will be set.
catch块中对应的就是最后一种情况:
If none of the previous conditions hold then this thread's interrupt status will be set.
会让当前线程重新恢复中断状态。
CountDownLatch:
public long worksCost(List works) throws InterruptedException {
CountDownLatch start = new CountDownLatch(1);
CountDownLatch end = new CountDownLatch(works.size());
for (Runnable work : works) {
new Thread(() -> {
try {
start.await();
try {
work.run();
} finally {
end.countDown();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
long times = System.currentTimeMillis();
start.countDown();
end.await();
return System.currentTimeMillis() - times;
}
FutureTask:
调用其get
方法时,需要处理2个异常:
InterruptedException
:当前等待get线程被打断。
ExecutionException
:执行过程出现异常。异常被封装成该类抛出。
以下例子展示了一个普遍的用法:
public class DataLoader {
FutureTask futureTask = new FutureTask(() -> {
return null;//sth need time to load from file or db
});
Thread thread = new Thread(futureTask);
public DataLoader() {
thread.start();
}
public Object load() throws InterruptedException {
try {
return futureTask.get();
} catch (ExecutionException e) {
if (e.getCause() instanceof SomeKnownException) {
throw (SomeKnownException)e.getCause();
} else {
throw SomeRuntimeException;
}
}
}
}
ExecutorCompletionService
实现了CompletionService
,可以简单的理解为:Executor
& BlockingQueue
的结合体,作为一组计算的句柄。避免了我们一直去手动轮询任务是否完成,而是直接借助一个阻塞队列,其内部原理其实就是重写FutureTask
的done
方法,在对应的task结束后将该task放入队列。源代码如图:
private class QueueingFuture extends FutureTask {
QueueingFuture(RunnableFuture task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future task;
}
如果想要这种效果:执行一组任务,一次性拿到结果集合(Future),并且还可以设定一个时间,时间到了还没有完成的任务cancel掉。那么使用invokeAll就可以很方便的完成。其实现位于AbstractExcutorService
该抽象类中:
public List> invokeAll(Collection extends Callable> tasks)
throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
ArrayList> futures = new ArrayList>(tasks.size());
boolean done = false;
try {
for (Callable t : tasks) {
RunnableFuture f = newTaskFor(t);
futures.add(f);
execute(f);
}
for (int i = 0, size = futures.size(); i < size; i++) {
Future f = futures.get(i);
if (!f.isDone()) {
try {
f.get();
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
}
}
}
done = true;
return futures;
} finally {
if (!done)
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
好处:
坏处