函数型语言
•Erlang:大量使用并发的时候使用
协作式和抢占式
•协作多线程、协作式系统
–每个任务自动放弃控制
–同时执行的线程数无限制
–适合处理大量的仿真元素
线程与设计
•线程使你能够创建更加松耦合的设计
•线程的使用
–实现Runable
–使用Thread
•Thread.yield——不能依赖
–将cpu从一个线程让给另一个线程
Thread的垃圾回收
•在它的任务退出run并死亡之前,垃圾回收器无法清除它
使用Executor
•可以管理Thread对象
•使用多线程的优选方法
•首选CachedThreadPool
–Executors.newCachedThreadPool()
•在线程内部,通过Thread.currentThread获得当前驱动任务线程的对象
SingleThreadExecutor
•如果向SingleThreadExecutor提交多个任务,这些任务会排队,每个任务按顺序执行
使用Callable从任务中返回值
•泛型
•必须使用ExecutorService.submit执行任务
•submit方法返回参数化的Future对象
休眠
•Old-style:
–Thread.sleep(100)
•Java5/6-style
–TimeUnit.MILLISECONDS.sleep(100);
•sleep会抛出InterruptedException,必须在run中捕获并处理异常,不能跨线程传播回main,因为在main中不能捕获由线程抛出的异常
优先级
•唯一可移植的优先级:
–Thread.MAX_PRIORITY
–Thread.MIN_PRIORITY
–Thread.NORM_PRIORITY
•线程初始化优先级
–Thread.NORM_PRIORITY
•优先级修改
–在run里面修改,Thread.currentThread().setPriority(priority);
后台线程
daemon——
不推荐
•Thread.setDaemon(true)
•当非后台线程结束后,后台线程自动结束,且不会执行finally子句,即,程序不能正常关闭。因此不推荐使用!
•使用ThreadFactory为ExecutorService创建后台线程
•后台线程创建的线程自动都是后台线程
join
•等待某线程结束后再继续执行
–A任务里调用B.join,说明A要等待B结束
•替代者:CyclicBarrier
异常捕获
•在run中的catch中捕获中断异常时,中断标志总是false的。因为已经被清除了。。。
•因为在main中不能捕获由线程抛出的异常,可以使用Thread.setDefaultUncaughtExceptionHandler设置默认的未捕获异常处理器
同步—synchronized
•对象自动含有锁
•当对象上任一synchronized方法被调用时,对象即被锁,其他线程只能等待锁被释放,才能继续调用对象上synchronized方法
•即一个对象的所有synchronized共享一个锁
•类也有锁, synchronizedstatic可以锁静态数据
何时同步
•每个访问临界共享资源的方法都必须被同步
•Biran同步规则
–如果你正在写一个变量,它可能被另一个线程读取,或正在读一个被另一个线程写过的变量,那必须使用同步,并且,读写线程必须用相同的监视器锁同步。
显示锁Lock
•Lock.lock/Lock.unlock
•在try中return,在finally中unlock
•Lock.tryLock尝试获取锁,成功就锁,失败就继续执行
•通常只在解决特殊问题时使用
原子性
•不正确的认识:
–原子操作不需要同步控制!
•除long和double之外的所有基本类型的简单操作(读/写)是原子操作
•当定义long或double变量时使用volatile,也可以获得原子性
可视性
•一个任务作出的修改,对另一个任务不一定是可视的,特别是做多处理器系统中,例如修改只暂存在本处理器的缓存中
•使用volatile,使修改对多cpu可见
volatile和synchronized
•只要对域做了写操作,就会被写入主存,其他所有任务的读操作就能读到这个修改。
•同步synchronized也会向主存中刷新,因此如果用了synchronized,就不必使用volatile
•首选synchronized,是最安全的
•在任何时候,如果有多个线程读写某个对象,就使用synchronized方法
原子类
•AtomicInteger/Long等类,可以代替锁
•对常规变成很少用,在性能调优时使用
•只在特殊情况下使用Atomic类,使用锁(synchronized 和Lock)更安全
使用同步控制块
•不会防止访问某整个方法,可显著提高性能
•synchronized(object){…..}得到的是object的锁
•一般使用synchronized(this){…..}
ThreadLocal
•根除对变量的共享
•为使用相同变量的每个线程创建不同的副本
•ThreadLocal通常是静态存储域
中断被阻塞的线程
•Thread.interrupt
•在ExecutorService上调用shutdownNow,会发送interrupt给它的所有线程
•使用ES的submit而不是executor来启动任务,通过返回的Future.cancle(true),可以发送interrupt给Future代表的线程中断线程。
•当任务试图执行已经被中断的阻塞时,将抛出InterruptedException
阻塞都能被中断吗?
•sleep能被中断
•IO阻塞不能被中断
–解决办法:关闭底层资源
–更人性化的方案:使用nio
•synchronized阻塞不能被中断
–使用显示Lock,在Lock上的阻塞可以被中断。Lock.lockInterruptibly方法
•任何不可中断的阻塞,都可能引起死锁
Run函数惯用法
public void run() {
int id=0;
try {
while (!Thread.interrupted()) {
// point1
Resource n1 = new Resource(++id);
// Start try-finally immediately after definition
// of n1, to guarantee proper cleanup of n1:
try {
print("Sleeping");
TimeUnit.SECONDS.sleep(1);
// point2
Resource n2 = new Resource(++id);
// Guarantee proper cleanup of n2:
try {
print("Calculating");
// A time-consuming, non-blocking operation:
for (int i = 1; i < 2500000; i++)
d = d + (Math.PI + Math.E) / d;
print("Finished time-consuming operation");
} finally {
n2.cleanup();
}
} finally {
n1.cleanup();
}
}
print("Exiting via while() test");
} catch (InterruptedException e) {
print("Exiting via InterruptedException");
}
}
wait、sleep、yield
•都会挂起线程
•wait会释放锁,表示某事已经做完,但sleep和yield不会
•wait、notify必须在synchronized方法/块中被调用(否则会抛出异常),sleep可以在非synchronized方法/块中被调用
汽车涂蜡-抛光
•涂蜡-WaxOn
–第一步:涂蜡
–进入下一场涂蜡之前,先等待抛光完成
•抛光-WaxOff
–进行抛光之前,必须先等待涂蜡完成
–第二步:抛光
等待惯用法:while
•使用while检查感兴趣的条件标志,当被唤醒时若发现条件不满足时,继续等待,直到条件满足
while(condition){
wait();
}
notify和notifyAll
•一般使用notifyAll,防止漏通知
•notifyAll只通知等待同一个对象锁的任务,而不会通知到等待其他对象锁定任务
显式的Lock和Condition对象
•Lock.newCondition
•Condition.await/signal/signalAll
•在调用condition的方法时,必须先获得lock
阻塞队列
•阻塞队列可以解决大量的问题,且与wait和notify方式比起来简单可靠得多…..
•Java.util.concurrent.BlockingQueue
•阻塞队列本身具有同步控制功能
•消除类之间的耦合
死锁
•哲学家就餐
•四个条件同时满足
–存在互斥资源
–持有且等待
–资源不能抢占
–循环等待(以某种特定的顺序获取资源)
•防止死锁
–破坏某个条件(循环等待最容易破坏掉)
CountDownLatch
•用于同步一组任务等待另一组任务完成
•方法
–await,调用该方法的线程将阻塞,直到latch的值减到0
–countDown,每调一次,latch减1
CyclicBarrier
•一组任务并行执行,但在进行下一步之前,必须等待其他任务把之前的工作都做完后,再同时进行下一步工作
•具有栅栏动作,当减到0时触发,在new的时候指定参数Runnable
•自动进行下一轮,可多次自动重用
•CountDownLatch只能用一次
DelayQueue
•是一个无界的阻塞队列,用于放置实现Delayed接口的元素
•通过时间到期唤醒任务
•队列有序,排序通过实现Comparable接口实现。
•只有到期的元素才能被取出,即getDelay返回小于等于0。若无到期元素,将阻塞,直到有到期元素
PriorityBlockingQueue
•按优先级排序的阻塞队列
•任务通过实现Comparable实现排序
•在队列为空时,阻塞队列的消费/读取任务任务
SynchronousQueue
•没有内部容量的阻塞队列
•每个put都必须等待一个take
ScheduledExecutor
•schedule
–运行一次
•scheduleAtFixedRate
–按频率重复运行
Semaphore
•计数信号量
•应用于对象池,池中对象创建代价比较高
•请求资源:acquire
•释放资源:release
Exchanger
•两个任务之间交换对象的栅栏
•用于生产者和消费者之间消费大对象的列表,可一步消费整个队列
•exchange(…)
银行出纳员仿真
•场景
–对象随机出现,并且要求由数量有限的服务器提供随机数量的服务时间
饭店仿真
汽车组装机器人
比较各类互斥技术
•以synchronized入手,只有在性能调优时才替换为Lock,只有在性能方面有明确的指标时,才替换为Atomic
•Atomic>Lock>synchronize
免锁容器
•CopyOnWriteArrayList
–写入时,创建新副本,原数据保持不变,仍可安全读取,当修改完成时,一个原子操作把新数据换入,使得新的读取操作可以看到这个修改。
•类似:
–CopyOnWriteArraySet
–ConcurrentHashMap、ConcurrentLinkedQueue
•如主要从免锁容器读数据,而少写,则比synchronized对应物快,因为加解锁的开销被省掉了
•SynchronizedList无论读取者和写入者的数量多少,都具有大致相同的性能
•CopyOnWriteArrayList在没有写入者时,速度快许多,在有5个写入者时,速度依然明显快。
•推荐使用CopyOnWrite和Concurrent….系列
乐观锁
•Atomic.compareAndSet
•决定在Atomic.compareAndSet失败,必须决定做什么事情?是很重要的问题
ReadWriteLock
•允许多个任务同时读
•只允许一个任务写,且写时不允许任何读
•一个相当复杂的工具,只有在需要提高性能时才需要用到
活动对象
•每个活动对象持有一个任务队列,任何时刻,只有一个任务在运行