高并发与多线程网络学习笔记(五)设计模式
观察者设计模式

核心思想
当被观察者发生变化时主动通知观察者,不需要观察者询问
使用场景
1.监控线程生命周期,线程发现异常时主动通知监控程序
读写锁分离
核心思想
多个线程读和多个线程写同一块数据的情况下,允许多线程同时读,但是有线程在读的时候不允许写操作,只允许一个时刻只有一个线程在写,即读写互斥,写写互斥,读读不互斥
实现思想
并发包有已经实现的工具可以直接用 ReentrantReadWriteLock
维护状态量,当其发生变化是相应的wait()和notify()
//当前有多少个线程写入
private int writingWriters = 0;
//当前多少个线程正在等待写入
private int waitingWriters = 0;
//当前多少个线程正在读
private int readingReaders = 0;
//是否偏好于写
private boolean preferWriter;
优点:适用于读多写少的场景
缺点:要保证读时共享数据不受影响,可能会不小心没有加写锁就写了
不可变对象模式
核心思想
- 不可变对象一定是线程安全,保证了不允许修改它的任何状态
- 可变对象不一定是线程安全
实现思想
- 没有set方法
- private和final的变量禁止赋值
- final类禁止继承重写方法
- 对于引用数据类型要使用clone方法,否则别人可以直接修改
使用场景
- String:不可变对象,没有synchronize方法却保证了线程安全
- StringBuffer:可变对象,全为synchronized方法保证线程安全
- StringBuilder:可变对象,不为synchronized方法不保证线程安全
- 基本数据类型都是不可变对象
优点:不用锁,快
缺点:浪费内存空间,因为总是返回新的对象
Future设计模式
核心思想
将客户端请求的处理过程从同步改为异步,以便将客户端解放出来,在服务端程序处理期间可以去干点其他事情,最后再来取请求的结果。好处在于整个调用过程中不需要等待,可以充分利用所有的时间片段,提高系统的响应速度。
实现思想

FutureService的设计模式值得学习,分离了具体业务和返回结果,增强可插拔性。
使用场景
等待模式:你定了一个蛋糕,老板娘给了你一个凭据让你下午来取,你早上就可以做别的事情,下午来取的时候,发现还要等一个小时,你坐在店里等。
回调模式:你定了一个蛋糕,你跟老板娘说我不需要你给我凭据了,我告诉你我的地址,你蛋糕做好之后就送过来给我吧(任务结束后主动调用通知函数)
保护性暂挂模式
核心思想
指的是当前线程在执行某个任务之前,需要检查某一条件,只有在该条件成立的情况下,当前线程才可以继续往下执行当前任务。
一般可以通过请求队列,当不满足条件时放入请求队列,满足后再唤醒取出请求。
经典实现
while(condition is not satisfied){
obj.wait()
}
Guard-suspend模式常用于线程之间的同步,使用这种方式可以防止循环检测,线程可以被动唤醒,唤醒之后还可以再次检查suspend的条件,防止条件失效。
使用场景
- 服务器处理请求先需要检查的条件,比如当前线程活跃数,心跳连接等
ThreadStorage
核心思想
ThreadLocal 仅供线程内部使用变量,与共享变量相反。
- Global :在当前线程中,任何一个点都可以访问到ThreadLocal的值。
- Local :该线程的ThreadLocal只能被该线程访问,一般情况下其他线程访问不到
使用API
private void testThreadLocal() {
Thread t = new Thread() {
ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();
@Override
public void run() {
super.run();
mStringThreadLocal.set("droidyue.com");
mStringThreadLocal.get();
}
};
t.start();
}
- 每个线程需要有自己单独的实例
- 实例需要在多个方法中共享,但不希望被多线程共享
实现方式
- 每个线程维护自己的一个ThreadLocalMap,key是threadLocal对象本身,value是Object值。
- 如果维护的是key是线程,value是threadLocal,这样会导致线程安全的加锁。

public void set(T value) {
// 得到当前线程对象
Thread t = Thread.currentThread();
// 这里获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 如果map存在,则将当前线程对象t作为key,要存储的对象作为value存到map里面去
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
使用注意事项
- 作为线程全局的map对象,对于set的值要注意管理,否则可能会为了避免不动旧代码不断地创建threadLocal对象。
- 线程首先ThreadLocal实例被线程的ThreadLocalMap实例持有,也可以看成被线程持有如果应用使用了线程池,那么之前的线程实例处理完之后出于复用的目的依然存活所以,ThreadLocal设定的值被持有,导致内存泄露。上面的逻辑是清晰的,可是ThreadLocal并不会产生内存泄露,因为ThreadLocalMap在选择key的时候,并不是直接选择ThreadLocal实例,而是ThreadLocal实例的弱引用。
CountDown设计模式
核心思想
将串行化的场景抽取部分进行并行化的操作以提高运行性
经典实现
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(5);
Service service = new Service(latch);
Runnable task = () -> service.exec();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(task);
thread.start();
}
System.out.println("main thread await. ");
latch.await();
System.out.println("main thread finishes await. ");
}
}
public class Service {
private CountDownLatch latch;
public Service(CountDownLatch latch) {
this.latch = latch;
}
public void exec() {
try {
System.out.println(Thread.currentThread().getName() + " execute task. ");
sleep(2);
System.out.println(Thread.currentThread().getName() + " finished task. ");
} finally {
latch.countDown();
}
}
使用注意事项
- countDown()和join()的区别是countDown可以控制串行的位置,不一定要线程执行完成等待线程才能继续,join()必须是调用线程结束后当前线程才能继续工作。
两阶段终止模式
核心思想
指的就是当希望结束一个线程的时候,送出一个终止请求,但是不会马上停止,做一些刷新工作。进入“终止处理中”,在该状态下,不会进行该线程日常工作任务的操作,而是进行一些终止操作
经典实现
将程序分成运行阶段和结束阶段,在运行阶段执行业务,在结束阶段释放资源
try{}
catch{}
finally{}

浙公网安备 33010602011771号