赞
踩
学习目标:提升自己的java水平
1、 巩固java基础—基础是最重要的
2、 基础性知识实战
3、 java火热的技术学习
4、 技术实战
5、计算机理论基础学习
6、前端语言学习
7、网站实战项目
本人从事java开发的工作有一段时间了,在工作的时候发现自己的技能提升遇到了瓶颈,所以想了一下需要制定一个为期3-6个月的学习计划来提升下自己,为了督促自己写下这个记录卡片,记录自己的学习路程,因为很久没有制定过学习的计划,所以先从一周的计划开始,因为比较容易实现,建立自信,给自己加个油,你的技术会越来越棒。
1、 java基础篇学习:基础很重要,每次提升自己前都会重新温习一下java基础,每次温习基础的时候都会有新的体会和认知,所以这次会用1-2周的时间去稳固一下自己的基础
2、 java火热技术学习:计划学习一下现在比较火的技术,主要是以现在项目中应用的技术为主,大致计划的学习框架:阿里的微服务,springcloud,double+Zookeper,Redis,RabbitMq,Es的技术,主要学习一下搭建,参考一下我们现在开发的项目理解一下框架的核心理念,看一些源码的核心,由浅入深的学习下知识;
3、计算机理论基础学习:非科班的开发工程师的痛,工作的时间越久,感觉这个更重要,所以会安排一些时间来重点学习一下,这个是学习完这个阶段提升的一个重点
4、前端语言学习:主要是为了实现前后端交互学习的一个立体展现的学习,这次提升中不要求精通,但是要求掌握,可以制作简单的页面,前端的主要技术以vue为主
1、 java基础篇学习计划与打卡
0712日学习 | 打卡 |
---|---|
多线程知识学习 | 完成 |
0713日学习 | |
io流知识学习 | 完成 |
0714日-0725 有事未进行更新学习 | |
有事未进行更新学习 | no |
0726日学习 | |
jvm知识学习 | 完成 |
0802日学习 | |
spring知识巩固 | 未完成 |
线程反射知识巩固 | 未完成 |
2、 java火热技术学习
3、计算机理论基础学习
4、前端语言学习
预定的学习产出
第一周:多线程与并发问题
1.1 线程的定义:(我理解的线程本质应该就是Thread-》run()这个是任务-》start 执行)线程是进程的一个实体,是进程的一条执行路径;线程的组成包含程序计算器和栈;创建线程的方式只有两种继承Thread,实现Runnable接口;其实本质上都是在重写run方法;线程池创建本质上也是实现runnable的一种,所以不能算是创建的方式;callable的实现本质上可以把它看成一个任务就像没有调用start的runnable,但是callable不能直接调用start方法,所以它不具备线程的功能,他只能放入future task中,通过thread或者线程池执行,callable功能比runnable更加强大,它可以有返回值和异常的处理,常用的方法是get()获取返回值,这个方法会产生阻塞,会一直等到任务执行完毕才返回和get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null
// callable的使用展示,thread和runnable的就不弄了,太基础了 import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class callableTest{ private final static Object lock = new Object(); static class MyCallable implements Callable<String>{ @Override public String call() throws Exception { System.out.println("这里可以进行一些业务逻辑的处理"); return "业务处理完的结果返回"; } } public static void main(String[] args) { //这个是将callable任务放到future task中 FutureTask<String> task = new FutureTask<>(new MyCallable()); //通过线程执行任务,这个位置正常开发的时候需要进行统一的封装 Thread thread = new Thread(task, "线程A"); thread.start(); try { //获取执行之后的结果 String s = task.get(); System.out.println(s); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
1.2 多线程的使用
1.3 并发问题的发生
1.4 如何解决并发问题
我将这3个问题放在一起解决,因为有了多线程就会出现并发问题,有了并发问题就要针对并发问题进行处理。
首先为什么使用多线程:1.效率问题,使用多线程可以有效的提高任务的处理速度;2.从cpu的角度来说,线程不可能一直占用进程,如果单线程在执行任务的时候让出进程后重新占有进程需要重新进行上下文切换,这个是很耗时的,Cpu做一件简单事件是1ns,但是内存做事件是1-10us(10的3-4ns);所以cpu做一个事件的过程需要等待内存100万的时间周,所以需要使用多线程去执行,同时因为多核的发展,可以有多个进程供线程使用,所以使用多线程可以大大的提升效率。
如何创建多线程:原始话的是手动管理Thread的线程数,但是有了线程池之后多线程的使用都会统一交由线程池进行管理;
线程池的种类:1)CachedThreadPool 这类线程没有核心线程数,只有非核心线程数,最大线程数设置为无限大,超时时间为60s,而且它的队列是SynchronousQueue,是无法插入任务的,每次有新任务都会重新建一个线程,所以这个线程池适用于任务大耗时小的情景-----影响:如果因业务场景考虑不周的话任务处理速度小于任务周期,那么不断新建的线程会很快吃满栈的内存oom,需要慎重适用;
2)FixedThreadPool:核心线程数固定,keep alive time无效,适用的是无解的linked blocking queue,这个线程池适用于任务固定的耗时长的场景。
3)ScheduledThreadPool:核心线程数固定非核心不固定,没有超时时间,这类线程池适用于执行定时任务和具体固定周期的重复任务,主要的两个方法
//第二个参数是延迟的时间大小,第三个是单位
pool.schedule(t4,10, TimeUnit.MILLISECONDS)
//延迟10秒执行,2秒执行一次
pool.scheduleAtFixedRate(t5,10,2,TimeUnit.SECONDS);。
4)SingleThreadPool:这类线程池顾名思义就是一个只有一个核心线程的线程池,从构造方法来看,它可以单独执行,也可以与周期线程池结合用。其任务队列是LinkedBlockingQueue,这是个无界的阻塞队列,因为线程池里只有一个线程,就确保所有的任务都在同一个线程中顺序执行,这样就不需要处理线程同步的问题。这类线程池适用于多个任务顺序执行的场景。
5)ThreadPoolExecutor:这个是通用的管理项目线程的线程池主要有以下几个参数:core’pool’si’ze核心线程数,mixmumpoolsize最大线程数,keep alive time存活时间,unit时间单位,thread Factory队列,handler拒绝策略;
线程池的不适用场景:
1.最近工作遇到一个需要优化响应时长的接口,现接口具体的场景大概就是,之前的一位大佬在正常的业务代码中增加了个异步线程去处理了一个业务逻辑(这个逻辑就是耗时最长的),但是在使用异步线程的时候,使用了get()方法获取返回值(使用这个方法会阻塞主线程的)进行下面的处理,后面的逻辑处理时间是很短的,优化前响应是2.5s优化后是2.45s没啥太大波动,为什么会这样的主要是应该之前的大佬没有具体去看代码,正确的优化方式1.异步线程处理逻辑,但是不要影响主流程的,不要对主流程进行阻塞,白话就是各干各的活不要有啥交集;2.针对具体处理慢的逻辑位置需要具体分析如果是多查询接口的话,可以通过多线程进行查询结果收集,这样是可以真正的提高接口效率的。
2.逻辑关联性强的接口不适用,这个之后实战写个案例吧
并发问题是如何发生的:1.多线程只要是涉及到数据共享(可见性valital)的都会存在线程安全的隐患;2.锁的不正确使用,导致的死锁;3.协同问题导致的未按照指定顺序执行业务操作;
如何处理并发问题:针对数据共享类的可分为处理可见性和数据变更性两类,可见性是因为没有加valital关键字导致在副本的数据和主内存的数据不一致导致的,数据变更的处理则需要加锁,sychronized/lock/cas进行数据唯一性变更操作;针对系统性问题也是可以使用锁机制和wait/notify/信号量/countdownlaunth针对不同的场景进行处理和使用(因为时间原因这里就先不写代码了,后面的实战日会附加上代码)
2、io流知识学习
链接: io的基础学习
.
其实流的用途就是将数据源读取到/读出内存的一个工具:分为输入流inputstream和输出流outputstream;输入输出流的使用网网会伴随着response和request的使用(后期会有实战代码练习–服务间调用获取,和直接向前端返回可以展示的);高级工具的使用表格类的读取,PDF的读取等(实战);
概念:io阻塞指的是在read()的时候如果数据没有就绪,那么线程会一直等待在那里,如果处理的数据比较大处理的速度慢的话是十分的吃内存的,严重的会造成oom,所以现在一般处理这个的方式一般分为两步,第一步规定制定的byte数量读取,第二步如果工作量大的话使用多线程进行读取;
3、jvm知识总结
学习jvm主要是为了在敲写代码的时候更好的提高系统的性能,因为jvm是系统实例和一些静态变量存放保存的关键,了解jvm就可以更好的把系统空间利用好。
3.1 jvm内存模型:网上有很多很好的图片归总,我这里就按内存类别归类了一下(主要是因为这个是出现问题的关键)
线程安全分类
内存私有区:程序计数器、栈
内存共享区:堆、方法区
oom
会出现的位置:堆、栈、方法区
3.2 介绍
程序计数器:用于记录线程执行字节码行号的指示器,每条线程都会创建一个自己程序计数器,这是为一块不会出现oom的位置
栈:每个方法的调用都会创建一个栈帧,栈帧又包含局部变量表(非基本类型是地址,基本类型会在局部变量表创建)、动态连接、返回出口、操作数栈,每个方法的执行就是一个栈帧的入栈到出栈的过程,这个位置和线程息息相关,所以出现oom的时候也可以考虑这个位置,因为一个栈的创建是需要消耗内存资源如果线程创建的过多会出现oom,同时栈帧的大量创建也会出现oom;
方法区:被jvm加载的类信息,静态变量,常量,及时编译器编译编译后的代码等数据;
堆:(jdk1.8,jdk最新的版本内存使用G1垃圾处理不区分年代)分为年轻代和老年代,年轻代又分为伊甸园和幸存区(幸存1和幸存2,分为两个主要是为了垃圾算法的复制算法),主要存放创建的对象和数组,是垃圾回收处理的重要位置
3.3 一些jvm设计理念
3.3.1 为什么会划分年轻代和老年代
设置分代处理主要目的是提高内存的使用率,提高使用率的根本就是管理对象的销毁和创建;堆的内存分为年轻代和老年代,默认内存比为1:2,,可以通过xx newratio 之所以需要老年代空间大是因为如果老年带满了会出发fullgc,频繁的fullgc会影响系统的性能,所以在条件允许的情况下尽量使老年代空间大,年轻代中分为Eden+s0+s1,默认比为8:1:1,通过xx :SurvivorRatio设置幸存区,年轻代设置这样的格式主要是为了处理大量新创建的对象大部分都会死亡,只有少部分幸存,所以Eden区的内存大一些,分为两个幸存是年轻代使用的是复制算法的垃圾回收机制,复制算法没有零碎话的内存碎片同时因为是复制清除,所以效率比较高,应对于这种存活少的情况是很适合使用的,同时每次幸存的对象都是会有幸存标识,默认是16次会进入老年代空间(XX:+MaxTenuringThreshold设置幸存),这样会大大减少对象进入老年代,在年轻代只会出发minor gc(耗时短),减少了老年代的full gc(耗时长)
3.3.2 垃圾回收算法
Serial收集器: 单线程的收集器,收集垃圾时,必须stop the world,使用复制算法。
ParNew收集器: Serial收集器的多线程版本,也需要stop the world,复制算法。
Parallel Scavenge收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。
Serial Old收集器: 是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。
Parallel Old收集器: 是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法。
CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。
G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。
2)CMS收集器和G1收集器的区别:
CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
CMS收集器以最小的停顿时间为目标的收集器;
G1收集器可预测垃圾回收的停顿时间
CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
3.3.3 gcroot有哪些
虚拟机栈中的对象,方法区中静态和常量对象,本地方法栈中jni引用的对象
3.3.4 oom的分析
思路:oom的位置,堆、栈、方法区,主要是从各个部分的存储信息来进行问题的具体分析,具体上面已经提到过,举个真是的线上问题,具体业务就不说了,后台的逻辑是使用了一个缓存线程池,正常的话是由一个请求的话会分发10个线程去处理相关业务,但是,前端的逻辑有问题导致1分钟内出现了1万次的请求,直接服务器宕掉了,后来通过后台的内存管理看到是栈的内存溢出了,所以上面的文章有提到过缓存线程池谨慎使用。
3.3.5 jvm的一次完整的gc(jdk1.8)
思路:内存+minor gc + minor gc + full gc
内存分为年轻代和老年代,年轻代又分为Eden区和S0,S1,当Eden满了的时候,会触发minor gc,幸存下的对象会进入S0,对象年龄加1,当Eden区再次满了的时候会再次触发minor gc,将幸存下的Eden和S0的对象复制到S1同时清除Eden和S0,年龄加1,如此往复,年龄到16(默认)的会被移动到老年代,正常情况下,到了老年代满了的时候会先触发年轻代的minor gc,如果minor gc还是无法满足,这个时候会触发full gc,另外有特殊情况就是幸存区满了,但是年龄没有到16,这个时候也会触发minorgc,如果年轻代还是无法满足的话,会将幸存区的对象直接放入老年代(这个时候需要调整一下虚拟机内存的分配)
1、 技术笔记 每个阶段至少2 遍
2、CSDN 技术博客 每个阶段至少1 篇
3、 学习的 vlog 视频 1 个
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。