Java基础、多线程、JUC常见锁机制、liunx及docker常见命令

Java基础小知识

重要

1.Java如何进行文件上传下载

//上传: 客户端向服务端发送数据
内存中已经有数据,只需要outputStream数据写到服务器就行
//下载: 服务器向客户端发送数据
需要先读取服务器数据到内存中,然后输出到客户端
FileInputStream fileInputStream = new FileInputStream(file);//将本地文件加载到内存中
//指定输出位置
OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());

2.为什么重写equals就需要重写HashCode

以八股的视角打开集合框架,在Map章节中详细的阐述了这个问题

3.==与equals之间的区别

== 判断的是地址值

Object中equals本质是==,String重写之后先用==判断地址值是否相等,然后判断值是否相等

4.动态代理机制(AOP使用)

代理机制,在不改变原有对象的基础上,扩展对象中的方法。

静态代理,在编译阶段创建代理对象。具体就是实现类实现接口,代理类实现同一接口,代理类将原接口设置为成员变量并且通过set方法赋值,最后代理类实现接口方法,在代理对象方法中调用实现类对应方法。这样就在不改变原有对象的情况下对代码进行增强,本质就是在代理类中写入增强代码然后调用实现类方法

动态代理,在运行时通过方法调用生成代理对象,不用在编译阶段一个个创建代理类。JDK动态只能代理实现过内容的类,但是速度快。CGlib动态代理可以代理任何类

JDK动态代理,newProxyInstance()方法创建代理对象,newProxyInstance方法会调用InvocationHandle中的invoke方法,在JDK动态代理中invoke方法就相当于实现类中要执行的方法,可以在这里写增强内容。

CGlib动态代理,通过enhance.getProxy()获取代理类,会调用MethodInterceptor中的intercpter进行代理类的增强

5.反射(IOC使用)

反射,在运行时动态获取类信息及其执行类当中的方法。在框架使用时,可以通过全类名获取类后,通过类去动态创建对象并且执行其中的方法。

通过反射创建对象的四种方式:

                1.知道具体类,类.Class获取对应的类

                2.通过全路径类名获取类,Class.forName(“类的全路径”)获取类

                3.通过对象获取类,Object o = new Object(),o.getClass()获取类

                4.通过类加载器获取类,XXXClassLoader.getClass(“全路径类名”)获取类

常见

1.String类中常用方法

length():返回字符串长度

charAt():获取指定索引位置的字符

indexOf():返回指定字符第一次出现的索引

substring():返回在beginIndex到endIndex的字符串

concat():合并字符串

2.string、stringBuffer、stringBuild区别,字符串如何修改

string不可以修改,stringBuffer修改字符串需要加锁,stringBuild修改字符串不加锁

速度上:stringBuild>stringBuffer。 string进行字符串之间的+操作本质是使用stringBuffer

3.进程与线程的区别

进程是操作系统进行资源分配和调度的基本单位,线程是进程内的执行单元,一个进程中可以包含多个线程

4.并行与并发的区别

并行:在同一时刻同时发生(在不同CPU中执行不同任务)

并发:在同一时间间隔内同时发生(人类认为是同时发现,其实是在CPU中轮换执行任务)

5.什么是泛型、泛型的作用

泛型就是让类、接口和方法能够在运行时动态获取业务所需要的类,从而提高代码的重用性。

6.java中static关键字使用位置、作用

位置:方法上,成员变量

作用:静态资源属于类资源。可以通过类名去调用静态变量或者静态方法。静态方法只能调用静态方法不能调用非静态方法。

7.Java异常机制

8.权限控制符

public:任何包中都可以访问

protect:同一个包或者子类

default:同一个包

private:只有类内部才能访问(匿名内部类也可以)

9.重载与重写之间的区别

方法重载:在同一个类当中。发法名相同,但是返回值、参数类型不同。侧重于完成一个功能可能有多种方式。

方法重写:发生在实现关系中。方法名相同,返回值、参数都相同。侧重于对方法功能的重新实现

10.抽象类与接口的区别

抽象类:继承关系  方法可以有抽象方法,具体实现方法。只能单继承

接口:实现关系   只能包含抽象方法。但是可以多实现

liunx、docker常用命令

liunx常用命令

cd:跳转文件目录

ls:显示当前文件夹中文件

mkdir:创建目录

查看日志的使用命令:

tail -f :将输出文件底部的数据

grep:通过 grep加关键字可以查询包含关键字的日志信息

docker常用命令

docker exec :进入容器内部

docker pull:拉取镜像

docker images:展示已有镜像

docker ps:展示当前运行容器        -a参数 查看所有容器

部署项目需要的步骤及其命令

nohup java -jar xxx.jar >log/xxx.log 2>&1 & 

nohup java 启动Java程序      

>log/xxx.log 输出日志文件到指定位置

2>&1 错误信息也打印到log日志中

部署自己的java项目到公网

多线程常识

线程的生命周期

new:初始状态,线程没有执行start()方法前

runnable:运行状态,线程执行start()方法之后

blocked:阻塞状态,等待锁释放

wait:等待状态,在同一个CPU当中其他线程竞争到了时间片,等待其他线程执行任务完毕

time_waitting:超时等待,超过一定时间直接返回默认处理值,不会像wait一样一直等待

terminated:终止状态,线程任务执行完毕

可以直接调用Thread中的run()方法吗?

start()方法方可启动线程并使线程进入就绪状态,而执行执行run()方法不会用多线程的方式启动。(start方法内部执行过程中会调用run方法,因为start方法是等cpu有时间处理线程任务才会执行,直接执行run方法相当于在主线程中执行一个普通方法)

多线程的创建方式(常考)

1.继承Thread类,重写run()方法

2.实现Runable类,重写run()方法。

3.实现Callable类,重写call()方法。该方法执行完毕会有返回值,通过返回值判断任务执行情况

这三种方式本质都是:new Thread().start()方法去创建线程。

线程池及参数详解(重要)

corePoolSize:核心线程数,任务未达到工作队列最大容量时,可以启用的线程数。

maximumPoolSize:最大线程数,任务达到工作队列最大容量时,当前可以运行的线程数从核心线程数转变为最大线程数。

KeepAliveTime:最大存活时间。线程数量超过核心线程数时,超过的线程不会立刻销毁,而是等到存活时间之后销毁。

unit:时间单位。最大存活时间的时间单位额。

workQueue:工作队列。新来的任务会判断运行的线程是否达到核心线程数,如果达到任务会添加到工作队列当中。(如果工作队列满了,会启动最大线程数执行任务)

threadFactor:线程工厂。创建线程用到

handler:拒绝策略。最大线程数满,工作队列满时。会拒绝任务添加。

拒绝策略: 抛出异常信息、将任务返回给调用者线程处理、丢弃新任务、丢弃最早的任务

创建线程池的两种方式:

通过Executor、ThreadPoolExecutor创建线程池。

JUC常见锁机制

ThreadLocal

ThreadLocal主要作用是给每一个线程创建单独资源,线程间的数据相互隔离。

ThreadLocal与synchronized的区别:

synchronized主要针对线程对共享资源访问,ThreadLocal给每一个线程创建单独副本,保证资源隔离。

ThreadLocal数据结构:

ThreadLocal内存溢出问题:

ThreadLocal的key为弱引用,在每次GC时会直接回收。Value为强引用,GC时不会回收。当GC来临时会出现很多空Key的Map一直存在内存当做就会出现内存泄漏。当然ThreadLocal考虑到这种情况,调用remove()方法就可以清除空Key的Map

volatile、synchronized关键字

volatile关键字,可以保证变量的可见性(会将线程变量存储到主存当中,线程获取该变量都需要去内存中取)。但是volatile不能保证操作的原子性

volatile关键字最重要的作用:禁止指令重排序(常用在单例模式)

new Object()对象会产生三步操作:分配内存空间->对象初始化->对象指向分配的内存地址

如果JVM进行指令重排序,就可能会造成线程1执行,分配内存空间、对象指向分配的内存地址。线程2稍后执行对象初始化。但是如果在线程1执行完毕,线程2未执行,线程3获取对象,那么这个时候线程3获取的是一个没有初始化赋值的对象。

volatile、synchronized关键字的区别:

volatile不能保证原子性,synchronized可以保证可见性和原子性,但是volatile速度快


inc++操作可以分为:
1.读取 inc 的值。
2.对 inc 加 1。
3.将 inc 的值写回内存。


如果使用volatile关键字修饰,那么多个线程可能都同时访问到读取inc值,那么他们+1多个线程只加一次。

如果使用synchronized就可以避免该问题,但是速度慢一些

Synchronized

使用位置:实例方法(锁对象)、静态方法(锁类)、代码块(锁指定类/对象)

Synchronized实现原理

乐观锁、悲观锁详解

悲观锁:共享资源每次只给一个线程使用其他线程阻塞,当资源使用完毕后再给其他线程。(例如:synchronized、ReenTrantLock)适合写多读少场景

乐观锁:乐观锁任务每次访问共享资源都不会出现资源竞争问题,使用的时候无需加锁,只需要在最后提交的时候验证一下即可。(实现机制:版本号机制、CAS)在大量修改操作中乐观锁可能会出现大量失败重试导致效率降低。在读多写少的场景更有优势

CAS(乐观锁)

CAS算法:C为当前内存地址值、A为预期值、S为修改后的目标值。CAS会先比较当前值与预期值是否相等,如果相等再将当前值修改为目标值,比较和修改操作为原子操作。(CAS主要作用是保证加锁过程中数据的原子性,例如AQS中修改state变量)

CAS的三个问题:

  1. 循环开销大。(因为在对比预期值时可能会多次失败,在while循环中就会多次尝试。针对自旋操作时间大问题,可以设置一个自旋次数,超过阈值直接挂起线程)
  2. 只能操作一个共享变量的原子操作。
  3. ABA问题(当线程1进行比较并交换时,线程2将当前值A改为B,线程3将当前值B改为A,对于线程1来说并没有问题,但是实际上值发生过改变,原子性就不能保证了。解决办法可以使用版本号机制,操作一次版本号+1,通过检查版本号来确保ABA不会发生)

AQS(悲观锁)

AQS是锁底层实现的基本组件,从本质上来说提供两种锁机制:共享锁和排他锁。排他锁就是多个线程竞争同一份共享资源最后只有一个线程能够获取,共享锁(读锁)就是多个线程都可以获取同一份资源。AQS作为互斥锁需要解决的三个问题AQS如何实现互斥,并且如何保证多线程更新共享变量的安全性、AQS用什么数据结构保存等待状态的线程,还需要考虑公平性和非公平性、如何实现等待线程的阻塞和唤醒

AQS如何实现互斥?

AQS记录了一个int类型的state,通过判断state的值判断锁竞争的状态(0表示没有线程竞争该资源,大于1表示有线程竞争到该资源)当线程竞争到资源就会将state修改为1,。但是多个线程去修改state就会出现线程安全问题,AQS通过CAS去确保修改state的安全性。

AQS用什么数据结构保存等待状态的线程,如何阻塞唤醒线程

未竞争到资源的线程通过unsafe类中的park方法,将线程阻塞并放到一个FIFO(先进先出)原则的双向队列。当获得锁资源的线程释放锁之后,根据先进先出原则获取从双向链表的头部去唤醒下一个线程。

锁竞争的公平性和非公平性?

AQS中的公平锁机制是,先去等待队列中判断是否有等待竞争锁的线程,如果有则需排队等待。

非公平锁是不管等待队列是否有竞争锁的线程,直接去尝试修改state

ReenTrantLock(可重入锁)

可重入性:当线程1获取锁之后,线程1可以再次获取到这把锁(其他线程互斥),他的目的是为了防止死锁。(可重入锁本质是悲观锁)

可重入锁实现原理:获取锁之后,锁监视器数据+1,释放锁时锁监视器-1。如果加锁后不及时释放锁就会出现死锁。(悲观锁通过判读数值是否等于1从而判断是否加锁,可重入锁通过将监控值进行加减实现可重入性)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值