Java 多线程 & 集合 & I/O &设计模式 知识整理

4、多线程与集合

4.1、多线程


是什么?

线程有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元,进程与线程区别如下:一个进程至少有一个线程.。
扩展:

进程与线程区别,线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这是进程与线程的重要区别。

线程状态转换:


为什么?

单线程慢,多核CPU,操作系统支持多线程,提高CPU利用率,读文件,解析文件,数据入库,服务间发送请求,任务并发时为了提高效率,都需要使用多线程

怎么做?

继承Thread类、实现Runnable接口或使用Future和Callable三种方式运行线程,比如,网络I/O操作时,需要每次启动新的线程。网络IO,阻塞IO的局限,使用线程池,避免线程数过多 耗尽JVM资源(虚拟机栈)或用户线程数,ThreadPoolFactory提供线程池构建工厂类,而concurrent包提供 闭锁 栅栏 等一系列可以保证线程安全性的类

好不好?

Java完美支持大多数场景的多线程并发

重要的线程知识点:

ThreadLocal

ThreadLocal可以实现每个线程绑定自己的值,即每个线程有各自独立的副本而互相不受影响。一共有四个方法:get, set, remove, initialValue。可以重写initialValue()方法来为ThreadLocal赋初值。SimpleDateFormat不是线程安全的,可以通过如下的方式让每个线程单独拥有这个对象:

private static final ThreadLocal<DateFormat> DATE_FORMAT = new ThreadLocal<DateFormat>(){
  @Override protected DateFormat initialValue(){
      return new SimpleDateFormat("yyyy-mm-dd");
  }  
};
private static final ThreadLocal<StringBuilder> stringBuilder = new ThreadLocal<StringBuilder>(){
    @Override protected StringBuilder initialValue(){
        return new StringBuilder(256);
    }
};


ThreadLocal不是用来解决对象共享访问问题的,而主要提供了线程保持对象的方法和避免参数传递的方便的对象访问方式。ThreadLocal使用场合主要解决多线程中数据因并发产生不一致的问题。ThreadLocal为每个线程中的并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,但大大减少了线程同步所带来的线程消耗,也减少了线程并发控制的复杂度。

通过ThreadLocal.set()将新创建的对象的引用保存到各线程的自己的一个map(Thread类中的ThreadLocal.ThreadLocalMap的变量)中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。ThreadLocalMap初始容量为16的数组table,如果个数超过threshold(capacity*2/3)之后就会按照原来容量的2倍进行扩容。每当有新的ThreadLocal添加到ThreadLocalMap的时候会通过nextHashCode()方法计算hash值:

nextHashCode.getAndAdd(HASH_INCREMENT);//HASH_INCREMENT = 0x61c88647;


然后根据key.threadLocalHashCode & (table.length - 1);计算出在table中的位置i,如果发生冲突,那就根据((i + 1 < len) ? i + 1 : 0)计算出新的位置i。只是找下一个可用空间并在其中插入元素(线性探测法)。其中0x61c88647为((根号5 -1 )/ 2)* 2的32次方,与斐波那契额和黄金分割有关,为了让哈希码能均匀地分布在2的n次方的数组内。

ThreadLocalMap中每个Entry的key(ThreadLocal实例)是弱引用,value是强引用(这点类似于WeakHashMap:https://www.cnblogs.com/skywang12345/p/3311092.html)。当把threadLocal实例置为null以后,没有任何强引用指向threadLocal实例,所以threadLocal将会被gc回收,但是value却不能被回收,因为其还存在于ThreadLocalMap的对象的Entry之中。只有当前Thread结束之后,所有与当前线程有关的资源才会被GC回收。所以如果在线程池中使用ThreadLocal,由于线程会复用,而又没有显示的调用remove的话的确是会有可能发生内存泄露的问题。线程复用还会产生脏数据。由于线程池会重用Thread对象,那么与Thread绑定时的类的静态属性ThreadLocal变量也会被重用。如果在实现的线程run()方法中不显式地调用remove()清理与线程相关的ThreadLocal信息,那么倘若下一个线程不调用set设置的初始值,就可能get到重用的线程信息,包括ThreadLocal所关联的线程对象的value值。

其实在ThreadLocalMap的get或者set方法中会探测其中的key是否被回收(调用expungeStaleEntry方法),然后将其value设置为null,这个功能几乎和WeakHashMap中的expungeStaleEntries()方法一样。因此value在key被gc后可能还会存活一段时间,但最终也会被回收,但是若不再调用get或者set方法时,那么这个value就在线程存活期间无法被释放。

ThreadLocal建议

ThreadLocal类变量因为本身定位为要被多个线程来访问,它通常被定义为static变量。
能够通过值传递的参数,不要通过ThreadLocal存储,以免造成ThreadLocal的滥用。
在线程池的情况下,在ThreadLocal业务周期处理完成时,最好显示的调用remove()方法,清空“线程局部变量”中的值。
在正常情况下使用ThreadLocal不会造成OOM, 弱引用的知识ThreadLocal,保存值依然是强引用,如果ThreadLocal依然被其他对象应用,线程局部变量将无法回收。

线程池

线程池的作用:利用线程池管理并复用线程、控制最大并发数等;实现任务线程队列缓存策略和饱和策略;实现某些与时间相关的功能,如定时执行、周期执行等;隔离线程环境。可以通过ThreadPoolExecutor来创建一个线程池:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) 


1、corePoolSize 表示常驻核心线程数。如果等于0,则任务执行完之后,没有任何请求进入时销毁线程池的线程;如果大于0,即使本地任务执行完毕,核心线程也不会被销毁(除非allowCoreThreadTimeOut设置为true)。向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,才会根据是否存在空闲线程,来决定是否需要创建新的线程。除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。

2、maximumPoolSize 表示线程池能够容纳同时执行的最大线程数(>0)。线程池中允许的最大线程数。线程池的阻塞队列满了之后,如果还有任务提交,如果当前的线程数小于maximumPoolSize,则会新建线程来执行任务。注意,如果使用的是无界队列,该参数也就没有什么效果了。如果maximumPoolSize与coolPoolSize相等,即是固定大小线程池。

3、keepAliveTime 表示线程池中的线程空闲时间,当空闲时间到达keepAliveTime值时,线程会被销毁,直到只剩下corePoolSize个线程为止。在默认情况下,当线程池的线程数大于corePoolSize时,这个参数才会起作用。但是当ThreadPoolExecutor的allowCoreThreadTimeOut变量设置为true时,核心线程超时后也会被回收,可以通过ThreadPoolExecutor.allowCoreThreadTimeOut(boolean value)设置。

4、unit 表示时间单位。keepAliveTime的时间单位通常是TimeUnit.SECONDS。

5、workQueue 表示缓存队列。对于无界队列,可以忽略该参数。

如果运行的线程数少于 corePoolSize,则Executor 始终首选添加新的线程,而不进行排队。
如果运行的线程数等于或多于 corePoolSize,则Executor 始终首选将请求加入队列,而不添加新的线程。
如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。
LinkedBlockingQueue:基于链表结构的有界/无界阻塞队列,FIFO。
SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作,反之亦然。
PriorityBlockingQueue:具有优先级的无界阻塞队列。
6、threadFactory 用于创建新线程。由同一个threadFactory创建的线程,属于同一个ThreadGroup,创建的线程优先级都为Thread.NORM_PRIORITY,以及是非守护进程状态。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)

7、handler 表示执行饱和策略的对象。当超过workQueue的任务缓存区上限的时候,就可以通过该策略处理请求。可以实现自己的拒绝策略,例如记录日志等等,实现RejectedExecutionHandler接口即可。可以拒绝策略有4种:
a. AbortPolicy:直接抛出异常RejectedExecutionException,默认策略
b. CallerRunsPolicy:调用者所在线程来运行该任务,此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
c. DiscardPolicy:直接丢弃任务
d. DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重新尝试执行任务(如果再次失败,则重复此过程)。

可以使用两个方法向线程池提交任务,分别为execute()和submit()方法。execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。submit()方法用于提交需要返回值的任务,线程池会返回一个Future类型的对象,通过这个对象可以判断任务是否执行成功。如Future future = executor.submit(task);

利用线程池提供的参数进行监控,参数如下:

getTaskCount():线程池需要执行的任务数量。
getCompletedTaskCount():线程池在运行过程中已完成的任务数量,小于或等于taskCount。
getLargestPoolSize():线程池曾经创建过的最大线程数量,通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
getPoolSize():线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。
getActiveCount():获取活动的线程数。

以上摘自:https://blog.csdn.net/u013256816/

AQS及锁实现原理

参考:https://blog.csdn.net/zhangdong2012/article/details/79983404

Volatile与Synchronized区别

Volatile两层含义:

1、内存可见,写操作先行于读操作

2、禁止指令重排序

区别参考:https://www.cnblogs.com/kaleidoscope/p/9506018.html

更多:

多线程-整体汇总:https://blog.csdn.net/zangdaiyang1991/article/details/85838518

Linux用户态与内核态:https://www.cnblogs.com/vampirem/p/3157612.html

4.2、集合类

参考:

HashMap&&JUC源码等解析:https://www.cnblogs.com/skywang12345/category/455711.html

红黑树:https://www.cnblogs.com/skywang12345/p/3245399.html

二叉树、红黑树、B树性能比较:https://blog.csdn.net/z702143700/article/details/49079107

为什么HashMap用红黑树:https://bbs.csdn.net/topics/392346931?page=1

Java反射原理:https://www.cnblogs.com/hongxinlaoking/p/4684652.html

Java集合详解:https://www.cnblogs.com/ysocean/p/6555373.html

HashMap原理及扩容条件:https://www.yidianzixun.com/article/0L7M9vzJ

HashMap链表插入是头部还是尾部:https://blog.csdn.net/qq_33256688/article/details/79938886

ConcurrentHashMap原理:https://www.cnblogs.com/skywang12345/p/3498537.html

                                            https://www.cnblogs.com/ITtangtang/p/3948786.html

理解putIfAbsent:https://www.cnblogs.com/hadoop-dev/p/6256372.html

ArrayList和LinkedList区别:https://blog.csdn.net/langjian2012/article/details/45039349

                                             https://blog.csdn.net/bjzhuhehe/article/details/72230559

CopyOnArrayList:https://blog.csdn.net/linsongbin1/article/details/54581787

CopyOnArrayList与Collections.synchronizedList的性能对比:https://blog.csdn.net/zljjava/article/details/48139465

5、IO

参考:

Java I/O基础之I/O模型分类&I/O多路复用技术:https://blog.csdn.net/zangdaiyang1991/article/details/86688000

几种I/O编程实践:https://blog.csdn.net/zangdaiyang1991/article/details/86695006

6、网络

6.1、Cookie与Session区别

Cookie

是什么?

当一个用户通过HTTP协议访问一个服务器的时候,服务器会将一些key/value键值对(Cookie)返回给客户端浏览器,并给这些数据加上一些限制条件,在条件符合时,这个用户下次访问这个服务器的时候,数据又被完整地带回给服务器。

为什么?

为了记录用户在一段时间内访问Web的行为路径。由于HTTP协议是无状态的协议,服务器无法知道下一次来访问的还是不是上次访问的用户,这样就无法针对特定用户的数据做缓存处理以提高性能。Cookie的作用正在于此,每次同一客户端发出的请求都会带有第一次访问时服务端设置的信息(一般放在HTTP Header信息中),这样服务端就可以根据Cookie值来划分访问的用户了。

好不好?

优点:使用简单,轻松解决分布式问题

缺点:1、存储限制,存储在浏览器中,大小和数量受限(Cookie个数的限制都是50个。总大小不超过4KB)

           2、Cookie管理的混乱,每个应用都有自己的Cookie

           3、安全问题,在浏览器中存储很容易被窃取或篡改

Session

是什么?
前面介绍了Cookie可以让服务端程序跟踪每个客户端的访问,但是每次客户端的访问都必须传回这些Cookie,如果Cookie很多,这无形地增加了客户端与服务端的数据传输量,而Session的出现正是为了解决这个问题。


同一个客户端每次和服务端交互时,不需要每次都传回所有的Cookie值,而是只要传回一个ID,这个ID是客户端第一次访问服务器的时候生成的,而且每个客户端是唯一的。这样每个客户端就有了一个唯一的ID,客户端只要传回这个ID就行了,这个ID通常是NANE为JSESIONID的一个Cookie。

好不好?
优点:安全,重复提交表单等场景也可以通过生成token来校验合法性

缺点:不容易在多台服务器之间共享。

怎么做?
既然Cookie有以上这些问题,Session也有它的好处,为何不结合使用Session和Cookie呢?下面是分布式Session框架可以解决的问题:
◎ Session配置的统一管理。
◎ Cookie使用的监控和统一规范管理。
◎ Session存储的多元化。
◎ Session配置的动态修改。
◎ Session加密key的定期修改。
◎ 充分的容灾机制,保持框架的使用稳定性。
◎ Session各种存储的监控和报警支持。
◎ Session框架的可扩展性,兼容更多的session机制如wapSession。
◎ 跨域名Session与Cookie如何共享,现在同一个网站可能存在多个域名,如何将Session和Cookie在不同的域名之间共享是一个具有挑战性的问题。

解决?

1、使用ZooKeeper等高可用的一致性协调组件来存储Cookie,统一管理Cookie,订阅推送公用Cookie。

2、采用分布式缓存系统,如Memcache或Tair等来存储session,分布式Session架构图:

总结:
Cookie和Session都是为了保持用户访问的连续状态,之所以要保持这种状态,一方面是为了方便业务实现,另一方面就是简化服务端程序设计,提高访问性能,但是这也带来了另外一些挑战,如安全问题、应用的分布式部署带来的Session的同步问题及跨域名Session的同步等一系列问题,而这些问题可以通过分布式Session框架来解决。

参考:

《深入分析Java Web技术内幕》

6.2、HTTP协议相关

参考:

HTTP学习之《图解HTTP》阅读:https://blog.csdn.net/zangdaiyang1991/article/details/84642797

7、面向对象及其设计原则

1、万物皆对象(Java反射原理-都是借助java.lang.class这个对象的属性、定义等实现)

2、面向对象的特征:封装(对象之间的隔离性、对象内部的属性封装)、继承(类的重用,耦合性变强,组合优于继承)、多态(继承,重载,覆写)

3、面向对象软件设计的六大设计原则

  • 里氏替换:能使用父类的地方也能透明的使用子类的实例
  • 单一职责:应该有且仅有一个原因引起类的变更
  • 迪米特:一个对象应该对其他对象有最少的了解
  • 依赖倒置:客户端不应该依赖它不需要的接口 & 类间的依赖关系应该建立在最小(函数少)的接口上
  • 接口隔离:高层模块、底层模块、细节都应该依赖于抽象(面向接口编程)
  • 开闭原则:类、模块和函数,应该对扩展开放,对修改关闭

六大设计原则(单一职责、里氏替换等):https://blog.csdn.net/hfreeman2008/article/category/6371386

4、软件设计的其他考虑

一个好的软件,需要满足:

从用户角度,简单易用,稳定(我们开发人员作为Spring的用户,Spring的设计初衷同样包括易用性)

从开发角度,代码整洁,可维护,可测试,架构可扩展,可伸缩,高性能,高可用,稳定

从运维角度,软件包小,配置集中管理

从测试角度,便于测试,稳定性高

更多:

OOA、OOD、OOP:https://blog.csdn.net/u010883077/article/details/43421269

                                   https://blog.csdn.net/qq_30402119/article/details/50068547

8、设计模式

桥接模式:https://blog.csdn.net/a19881029/article/details/80979200

命令模式:https://blog.csdn.net/a19881029/article/details/9013727

参考:

观察者、责任链:https://blog.csdn.net/zangdaiyang1991/article/details/84189437

设计模式:https://blog.csdn.net/a19881029/article/category/1400727

                  https://hiddenpps.blog.csdn.net/column/info/sjmsxgjs

                  https://blog.csdn.net/xsfqh/article/details/80559510

猜你喜欢

转载自blog.csdn.net/zangdaiyang1991/article/details/89892938