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

观察者设计模式

核心思想

当被观察者发生变化时主动通知观察者,不需要观察者询问

使用场景

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设计模式

核心思想

将客户端请求的处理过程从同步改为异步,以便将客户端解放出来,在服务端程序处理期间可以去干点其他事情,最后再来取请求的结果。好处在于整个调用过程中不需要等待,可以充分利用所有的时间片段,提高系统的响应速度。

实现思想

不减商山: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实例的弱引用。

理解Java中的ThreadLocal - 技术小黑屋​droidyue.com

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{}
posted @ 2021-01-17 15:01  王者之剑KO  阅读(132)  评论(0)    收藏  举报