1. 并发的多面性:
2. 线程基础:
2.1 Java中实现线程的方法:
2.2 有返回的的任务:Callable(还可以抛出异常)和Future;
2.3 休眠:
2.4 优先级(MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY):
2.5 让步(yield函数):
2.6 后台线程(Thread.setDaemon方法):
2.7 自管理Runnable:
class SelfManagerRunnbale implements Runnable {
private Thread thread = new Thread(this);
public void run() {
//...
}
}
2.8 加入一个线程(join):
在一个线程中对另一个线程调用join(),原线程挂起(可以通过isAlive查看)执行直到join的线程运行结束。
在调用线程中调用join线程的interrupt()方法可以中断join线程。
在catch到InterruptedException后,将清除interrupt标志,调用isInterrupt返回false;
2.9 捕获异常(典型的捕获子线程中逃逸的RuntimeException):
如何在线程外捕获逃逸的异常(RunntimeException);Java SE5后,可以用Executor解决这个问题——Thread.UncaughtExceptionHandler.uncaughtException();
使用Executor我们可以通过传入特定ThreadFactory的方式设置Thread的特性,这是一种很好的分离,好的设计。
另外,在一个线程中捕获子线程异常,可以通过设置默认异常处理器的方式:
Thread.setDefaultUncaughtExceptionHandler();默认处理器在子线程没有设置异常处理器时调用。
3. 共享受限资源:
超过一个线程处理临界数据,必须使用相关同步方法,每个有操作的方法都必须同步。存在过个线程共享统一资源时,使用序列化访问共享资源方案,通常使用锁语句,这是一种互斥量。
使用并发时,将域设置成private,防止其他任务直接访问域,用synchronized关键字锁定;
JVM锁跟踪计数,同一任务每进入一次synchronized时计数加1,每出一次synchronized计数减一;
java中lock和synchronized临界区都是可重入的锁;
3.1 synchronized关键字:
Java中对象和类都有对应的锁,可以保证synchronized static在类范围控制并发。
3.2 使用显式的Lock对象:
使用显式的Lock对象可以获得更细粒度的控制力。ReentrantLock类:与try-finally结合使用
try {
lock.lock();
} finally {
lock.unlock();
}
异常清理:
使用try-finally的好处是可以在发生异常事在finally做清理工作,单单使用synchronized不行,同时保证unlock不会过早发生;
尝试锁定(synchronized无法做到):
try {
captured = lock.trylock(2, timeout);
} finally {
if(caputred)
lock.unlock();
}
实现锁耦合(链式列表遍历节节加锁);
3.3 原子性与易变性:
volatile:保证64位数据类型读写的原子性(防止字撕裂);可见性(数据存入主存,没有本地缓存);不能保证递增/减操作或者某个域受到其他域限制的情况下的线程安全;原子类:Atomic类,用于保证一些读写操作的原子性;
临界区(同步控制块):synchronized(){};
在其他对象上同步:synchronized(object),如果获取了synchronized块上的锁,那么该对象其他synchronized方法和临界区就不能调用;
线程本地存储:一种自动化机制,为使用相同变量的每个不同的线程都创建不同的存储。ThreadLocal<T>,通过get/set方法存取内容;
4. 终结任务:
4.1 正常情况下安全退出的一种自定义方法:使用volatile的布尔值变量作为canceled标志,退出任务;
4.2 在阻塞时终结:
4.3 中断:
5. 线程间协作:
5.1 基础知识:
notifyAll:唤醒对应锁上所有挂起的线程;
使用显示Lock和Condition条件对象:
JSE 1.5 的新工具,可以从lock对象中获得condition,调用condition对象上的await,signal,signalAll来代替wait,notify,notifyAll,更为安全;
while(someCondition) {
//这里someCondition可能被其他线程修改,造成了无限的wait
synchronized(shareMonitor) {
sharedMonitor.wait();
}
}
5.2 消费者和生产者:
服务员:
class Waitress implements Runnable {
private final Restaurant restaurant;
public Waitress(Restaurant restaurant) {
this.restaurant = restaurant;
}
@Override
public void run() {
try {
while(!Thread.interrupted()) {
synchronized (this) {
while(restaurant.meal == null) {
wait();
}
}
//got a meal
logger.debug("got meal " + restaurant.meal);
synchronized (restaurant.cook) {
restaurant.meal = null;
restaurant.cook.notifyAll();
}
}
} catch (InterruptedException e) {
logger.error(e);
}
}
}
厨师:
class Cook implements Runnable {
private final Restaurant restaurant;
public Cook(Restaurant restaurant) {
this.restaurant = restaurant;
}
@Override
public void run() {
try {
while(!Thread.interrupted()) {
synchronized (this) {
while (restaurant.meal != null) {
wait();
}
}
//check count
if(++restaurant.count == 10) {
logger.debug("out of food, stop");
restaurant.executorService.shutdownNow();
}
synchronized (restaurant.waitress) {
restaurant.meal = new Meal(restaurant.count);
logger.debug("cooking " + restaurant.meal);
TimeUnit.MILLISECONDS.sleep(500); //here, throw a InterruptedException when shutdownNow
restaurant.waitress.notifyAll();
}
}
} catch (InterruptedException e) {
logger.error(e);
}
}
}
注意在厨师的run(),restaurant.executorService.shutdownNow()后,厨师线程并没有立刻停止;依然可以执行到logger.debug("cooking " + restaurant.meal);直到调用sleep()抛出java.lang.InterruptedException: sleep interrupted;前面也提到了sleep是对中断敏感的方法;
5.3 生产者和消费者与队列:
使用wait/notify的方式较为原始且很难控制,JAVA提供了更高的抽象:同步队列(阻塞队列):
阻塞队列的种类:
ArrayBlockingQueue:固定大小,可以实现公平和非公平;
LinkedBlockingQueue:基于链表实现的一个阻塞队列,如不指定容量,默认容量为Integer.MAX_VALUE;
PriorityBlockingQueue:出队顺序和优先级有关,无界阻塞队列,不进行容量检查;
DelayQueue:基于PriorityQueue,只有元素指定的延时到了,才可以获取数据;
重要方法:
add:不能传入null,否则抛出NPE,队列满(有界队列)时,抛出异常IllegalStateException;成功返回true;
remove:队列为空时抛出异常,成功返回true;
offer:插入元素到队尾,成功返回true;
peek:返回队首元素,空时返回null;
poll:返回并移除队首元素,空时返回努力来;
实现原理:通过Lock,Condition实现,put,take方法的等待条件分别是while(count == length)和while(count==0);
PS:任务间管道I/O:
使用PipedWriter/Reader进行通信;
没有阻塞队列健壮;
可以被中断;
6. 死锁:
发生的条件:(1)互斥条件;
(2)保持与请求条件;
(3)独占条件(不能剥夺);
(4)循环等待条件;