volatile底层实现原理(volatile关键字是什么)
本文目录
- volatile关键字是什么
- volatile关键字在Java中有什么作用
- 要当Java工程师,需要掌握什么技能
- C++底层是如何实现的
- 如果你是面试官,我来应聘一年java后台开发经验,你会问什么
- Java学到什么程度才能叫精通
volatile关键字是什么
主要从以下三点讲解 volatile 关键字:
- volatile 关键字是什么?
- volatile 关键字能解决什么问题?使用场景是什么?
- volatile 关键字实现的原理?
volatile 关键字是什么?
在 Sun 的 JDK 官方文档是这样形容 volatile 的:
The Java programming language provides a second mechanism, volatile fields, that is more convenient than locking for some purposes. A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable.也就是说,如果一个变量加了 volatile 关键字,就会告诉编译器和 JVM 的内存模型:这个变量是对所有线程共享的、可见的,每次 JVM 都会读取最新写入的值并使其最新值在所有 CPU 可见。volatile 可以保证线程的可见性并且提供了一定的有序性,但是无法保证原子性。在 JVM 底层 volatile 是采用内存屏障来实现的。
通过这段话,我们可以知道 volatile 有两个特性:
保证可见性、不保证原子性
- 禁止指令重排序
原子性和可见性
原子性是指一个操作或多个操作要么全部执行并且执行的过程不会被任何因素打断,要么都不执行。性质和数据库中事务一样,一组操作要么都成功,要么都失败。看下面几个简单例子来理解原子性:
i == 0; //1
j = i; //2
i++; //3
i = j + 1; //4
在看答案之前,可以先思考一下上面四个操作,哪些是原子操作?哪些是非原子操作?
答案揭晓:
1——是:在Java中,对基本数据类型的变量赋值操作都是原子性操作(Java 有八大基本数据类型,分别是byte,short,int,long,char,float,double,boolean)
2——不是:包含两个动作:读取 i 值,将 i 值赋值给 j
3——不是:包含了三个动作:读取 i 值,i+1,将 i+1 结果赋值给 i
4——不是:包含了三个动作:读取 j 值,j+1,将 j+1 结果赋值给 i
也就是说,只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。
注:由于以前的操作系统是 32 位, 64 位数据(long 型,double 型)在 Java 中是 8 个字节表示,一共占用 64 位,因此需要分成两次操作采用完成一个变量的赋值或者读取操作。随着 64 位操作系统越来越普及,在 64 位的 HotSpot JVM 实现中,对64 位数据(long 型,double 型)做原子性处理(由于 JVM 规范没有明确规定,不排除别的 JVM 实现还是按照 32 位的方式处理)。
在单线程环境中我们可以认为上述步骤都是原子性操作,但是在多线程环境下,Java 只保证了上述基本数据类型的赋值操作是原子性的,其他操作都有可能在运算过程中出现错误。为此在多线程环境下为了保证一些操作的原子性引入了锁和 synchronized 等关键字。
上面说到 volatile 关键字保证了变量的可见性,不保证原子性。原子性已经说了,下面说下可见性。
可见性其实和 Java 内存模型的设定有关:Java 内存模型规定所有的变量都是存在主存(线程共享区域)当中,每个线程都有自己的工作内存(私有内存)。线程对变量的所有操作都必须在工作内存中进行,而不直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。
举个简单栗子:
比如上面 i++ 操作,在 Java 中,执行 i++ 语句:
执行线程首先从主存中读取 i(原始值)到工作内存中,然后在工作内存中执行运算 +1 操作(主存的 i 值未变),最后将运算结果刷新到主存中。
数据运算是在执行线程的私有内存中进行的,线程执行完运算后,并不一定会立即将运算结果刷新到主存中(虽然最后一定会更新主存),刷新到主存动作是由 CPU 自行选择一个合适的时间触发的。假设数值未更新到主存之前,当其他线程去读取时(而且优先读取的是工作内存中的数据而非主存),此时主存中可能还是原来的旧值,就有可能导致运算结果出错。
以下代码是测试代码:
package com.wupx.test;/**
* @author wupx
* @date 2019/10/31
*/
public class VolatileTest {
private boolean flag = false;
class ThreadOne implements Runnable {
@Override
public void run() {
while (!flag) {
System.out.println(“执行操作“);
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(“任务停止“);
}
}
class ThreadTwo implements Runnable {
@Override
public void run() {
try {
Thread.sleep(2000L);
System.out.println(“flag 状态改变“);
flag = true;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String args) {
VolatileTest testVolatile = new VolatileTest();
Thread thread1 = new Thread(testVolatile.new ThreadOne());
Thread thread2 = new Thread(testVolatile.new ThreadTwo());
thread1.start();
thread2.start();
}
}
上述结果有可能在线程 2 执行完 flag = true 之后,并不能保证线程 1 中的 while 能立即停止循环,原因在于 flag 状态首先是在线程 2 的私有内存中改变的,刷新到主存的时机不固定,而且线程 1 读取 flag 的值也是在自己的私有内存中,而线程 1 的私有内存中 flag 仍未 false,这样就有可能导致线程仍然会继续 while 循环。运行结果如下:
执行操作执行操作
执行操作
flag 状态改变
任务停止
避免上述不可预知问题的发生就是用 volatile 关键字修饰 flag,volatile 修饰的共享变量可以保证修改的值会在操作后立即更新到主存里面,当有其他线程需要操作该变量时,不是从私有内存中读取,而是强制从主存中读取新值。即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
指令重排序
一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
比如下面的代码
int i = 0;
boolean flag = false;
i = 1; // 1
flag = true; // 2
代码定义了一个 int 型变量,定义了一个 boolean 类型变量,然后分别对两个变量进行赋值操作。从代码顺序上看,语句 1 是在语句 2 前面的,那么 JVM 在真正执行这段代码的时候会保证语句 1 一定会在语句 2 前面执行吗?不一定,为什么呢?这里可能会发生指令重排序(InstructionReorder)。
语句 1 和语句 2 谁先执行对最终的程序结果并没有影响,那么就有可能在执行过程中,语句 2 先执行而语句 1 后执行。
但是要注意,虽然处理器会对指令进行重排序,但是它会保证程序最终结果会和代码顺序执行结果相同,那么它靠什么保证的呢?再看下面一个例子:
int a = 10; // 1int r = 2; // 2
a = a + 3; // 3
r = a * a; // 4
这段代码执行的顺序可能是 1-》2-》3-》4 或者是 2-》1-》3-》4,但是 3 和 4 的执行顺序是不会变的,因为处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令 Instruction2 必须用到 Instruction1 的结果,那么处理器会保证 Instruction1 会在 Instruction2 之前执行。
虽然重排序不会影响单个线程内程序执行的结果,但是多线程呢?下面看一个例子:
// 线程1String config = initConfig(); // 1
boolean inited = true; // 2
// 线程2
while(!inited){
sleep();
}
doSomeThingWithConfig(config);
上面代码中,由于语句 1 和语句 2 没有数据依赖性,因此可能会被重排序。假如发生了重排序,在线程 1 执行过程中先执行语句 2,而此时线程 2 会以为初始化工作已经完成,那么就会跳出 while 循环,去执行 doSomeThingWithConfig(config) 方法,而此时 config 并没有被初始化,就会导致程序出错。
从上面可以看出,指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。
那么 volatile 关键字修饰的变量禁止重排序的含义是:
当程序执行到 volatile 变量的读操作或者写操作时,在其前面的操作肯定已经全部进行,且对后面的操作可见,在其后面的操作肯定还没有进行
- 在进行指令优化时,不能将 volatile 变量之前的语句放在对 volatile 变量的读写操作之后,也不能把 volatile 变量后面的语句放到其前面执行
举个栗子:
x=0; // 1
y=1; // 2
volatile z = 2; // 3
x=4; // 4
y=5; // 5
变量z为 volatile 变量,那么进行指令重排序时,不会将语句 3 放到语句 1、语句 2 之前,也不会将语句 3 放到语句 4、语句 5 后面。但是语句 1 和语句 2、语句 4 和语句 5 之间的顺序是不作任何保证的,并且 volatile 关键字能保证,执行到语句 3 时,语句 1 和语句 2 必定是执行完毕了的,且语句 1 和语句 2 的执行结果是对语句 3、语句 4、语句 5是可见的。
回到之前的例子:
// 线程1
String config = initConfig(); // 1
volatile boolean inited = true; // 2
// 线程2
while(!inited){
sleep();
}
doSomeThingWithConfig(config);
之前说这个例子提到有可能语句2会在语句1之前执行,那么就可能导致执行 doSomThingWithConfig() 方法时就会导致出错。
这里如果用 volatile 关键字对 inited 变量进行修饰,则可以保证在执行语句 2 时,必定能保证 config 已经初始化完毕。
volatile 应用场景
synchronized 关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而 volatile 关键字在某些情况下性能要优于 synchronized,但是要注意 volatile 关键字是无法替代 synchronized 关键字的,因为 volatile 关键字无法保证操作的原子性。通常来说,使用 volatile 必须具备以下三个条件:
对变量的写入操作不依赖变量的当前值,或者能确保只有单个线程更新变量的值
- 该变量不会与其他状态变量一起纳入不变性条件中
- 在访问变量时不需要加锁
上面的三个条件只需要保证是原子性操作,才能保证使用 volatile 关键字的程序在高并发时能够正确执行。建议不要将 volatile 用在 getAndOperate 场合,仅仅 set 或者 get 的场景是适合 volatile 的。
常用的两个场景是:
状态标记量
volatile boolean flag = false;
while (!flag) {
doSomething();
}
public void setFlag () {
flag = true;
}
volatile boolean inited = false;
// 线程 1
context = loadContext();
inited = true;
// 线程 2
while (!inited) {
sleep();
}
doSomethingwithconfig(context);
- DCL 双重校验锁-单例模式
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {
}
/**
* 当第一次调用getInstance()方法时,instance为空,同步操作,保证多线程实例唯一
* 当第一次后调用getInstance()方法时,instance不为空,不进入同步代码块,减少了不必要的同步
*/
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
使用 volatile 的原因在上面解释重排序时已经讲过了。主要在于 instance = new Singleton(),这并非是一个原子操作,在 JVM 中这句话做了三件事情:
给 instance分配内存
- 调用 Singleton 的构造函数来初始化成员变量
- 将 instance 对象指向分配的内存库存空间(执行完这步 instance 就为非 null 了)
但是 JVM 即时编译器中存在指令重排序的优化,也就是说上面的第二步和第三步顺序是不能保证的,最终的执行顺序可能是 1-2-3,也可能是 1-3-2。如果是后者,线程 1 在执行完 3 之后,2 之前,被线程 2 抢占,这时 instance 已经是非 null(但是并没有进行初始化),所以线程 2 返回 instance 使用就会报空指针异常。
volatile 特性是如何实现的呢?
前面讲述了关于 volatile 关键字的一些使用,下面我们来探讨一下 volatile 到底如何保证可见性和禁止指令重排序的。
在《深入理解Java虚拟机》这本书中说道:
观察加入volatile关键字和没有加入 volatile 关键字时所生成的汇编代码发现,加入 volatile 关键字时,会多出一个 lock 前缀指令。接下来举个栗子:
volatile 的 Integer 自增(i++),其实要分成 3 步:
读取 volatile 变量值到 local
- 增加变量的值
- 把 local 的值写回,让其它的线程可见
这 3 步的 JVM 指令为:
mov 0xc(%r10),%r8d ; Load
inc %r8d ; Increment
mov %r8d,0xc(%r10) ; Store
lock addl $0x0,(%rsp) ; StoreLoad Barrier
lock 前缀指令实际上相当于一个内存屏障(也叫内存栅栏),内存屏障会提供 3 个功能:
它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成(满足禁止重排序)
- 它会强制将对缓存的修改操作立即写入主存(满足可见性)
- 如果是写操作,它会导致其他 CPU 中对应的缓存行无效(满足可见性)
volatile 变量规则是 happens-before(先行发生原则)中的一种:对一个变量的写操作先行发生于后面对这个变量的读操作。(该特性可以很好解释 DCL 双重检查锁单例模式为什么使用 volatile 关键字来修饰能保证并发安全性)
总结
变量声明为 volatile 类型时,编译器与运行时都会注意到这个变量是共享的,不会将该变量上的操作与其他内存操作一起重排序。volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取 volatile 类型的变量时总会返回最新写入的值。
在访问 volatile 变量时不会执行加锁操作,也就不会使执行线程阻塞,因此 volatile 变量是比 sychronized 关键字更轻量级的同步机制。
加锁机制既可以确保可见性和原子性,而 volatile 变量只能确保可见性。
想了解更多Java相关,百度搜索圈T社区www.aiquanti.com,免费视频教程。纯干货
volatile关键字在Java中有什么作用
volatile是防止指令重排序来保证可见性
对于JVM层面是防止编译器的重排序
同时,对于有些cpu来说,他们会通过缓存锁或者中线索来解决缓存可见性
但是,目前很多cpu都做了优化,因为缓存一致性MESI会带来性能开销,所以用到了storebuffer机制来做异步处理,而这种机制带来了指令的乱序执行。从而导致可见性问题。
那么volatile会在cpu层面会增加内存屏障,来解决cpu的乱序执行带来的可见性问题
要当Java工程师,需要掌握什么技能
以下是查阅资料所得(Java工程师应具备的)
1.熟练的使用产品原型工具Axure,熟练的使用设计建模工具PowerDesigner和Enterprise Architect,熟练的使用Java开发环境Eclipse和IntelliJ,熟练的使用前端开发环境WebStorm,熟练的使用软件版本控制工具SVN和Git,熟练的使用项目构建和管理工具Maven和Gradle。
2、掌握Java语言进行面向对象程序设计,熟悉常用的Java API,包括集合框架、多线程(并发编程)、I/O(NIO)、Socket、JDBC、XML、反射等。
3、熟悉基于JSP和Servlet的Java Web开发,对Servlet和JSP的工作原理和生命周期有深入了解,熟练的使用JSTL和EL编写无脚本动态页面,有使用监听器、过滤器等Web组件以及MVC架构模式进行Java Web项目开发经验。
4、 对Spring的IoC容器和AOP原理有深入了解,熟练的运用Spring框架管理各种Web组件及其依赖关系,熟练的使用Spring进行事务、日志、安全性等的管理,有使用SpringMVC作为表示层技术以及使用Spring提供的持久化支持进行Web项目开发的经验,熟悉Spring对其他框架的整合。
5、熟练的使用Hibernate、MyBatis等ORM框架,熟悉Hibernate和MyBatis的核心API,对Hibernate的关联映射、继承映射、组件映射、缓存机制、事务管理以及性能调优等有深入的理解。
6、熟悉常用的关系型数据库产品(MySQL、Oracle),熟练的使用SQL和PL/SQL进行数据库编程。
7、熟悉面向对象的设计原则,对GoF设计模式和企业应用架构模式有深入的了解和实际开发的相关经验,熟练的使用UML进行面向对象的分析和设计,有TDD(测试驱动开发)和DDD(领域驱动设计)的经验。
8、熟悉Apache、NginX、Tomcat、WildFly、Weblogic等Web服务器和应用服务器的使用,熟悉多种服务器整合、集群和负载均衡的配置。
9、熟练的使用HTML、CSS和JavaScript进行Web前端开发,熟悉jQuery和Bootstrap,对Ajax技术在Web项目中的应用有深入理解,有使用前端MVC框架(AngularJS)和JavaScript模板引擎(HandleBars)进行项目开发的经验。
当然,这些还是要根据不同的招聘需求来的,每个公司需求的工程师也是不一样的。
比如来看:
学习Java一定要循序渐进,我分享一下我学习时的知识点,已经相对于看的视频,希望能对你有帮助把。
配套视频:
C++底层是如何实现的
其实编译这件事情从实现原理上看蛮蠢的,就是按套路写汇编而已,编译器作者提前把所有的语法都列举出来,然后一种一种写清楚每个语法对应到什么汇编上,比如说函数开头固定是几条汇编,遇到if翻译成什么汇编,遇到for翻译成什么汇编,遇到加法、减法各自是什么汇编等等。具体实现上现在可以分为直接到目标机器的汇编,和先到中间码(如LLVM)再到目标机器汇编等。
所谓编译优化,其实基本也是固定的,遇到什么条件则将之前的写法换成另一个写法,遇到什么条件可以将两段汇编合成一段,等等。
条件堆砌的多了,反而显得编译出来得汇编很“聪明”了,其实都是按规则生成的,编译器不会为你写的程序“灵机一动”将它变成别的形式,但这种朴实的作风带来的是绝对的正确性。
首先,指针在机器层面是非常简单的东西。你不带指针的操作可能是这样:
int add(int a, int b)
{
return a+b;
}
编译成伪汇编:
从栈顶寄存器+偏移a的位置,取四个字节到通用寄存器1
从栈顶寄存器+偏移b的位置,取四个字节到通用寄存器2
在通用寄存器1和通用寄存器2执行四字节整数加法,结果在通用寄存器2
将通用寄存器2存储四个字节,到栈顶+RESULT的位置
带指针:
int add(int* a, int* b)
{
return *a+*b;
}
编译成伪汇编:
从栈顶寄存器+偏移a的位置,取八个字节到通用寄存器1
按照通用寄存器1的内容作为位置,取四个字节到通用寄存器1
从栈顶寄存器+偏移b的位置,取八个字节到通用寄存器2
按照通用寄存器2的内容作为位置,取四个字节到通用寄存器2
在通用寄存器1和通用寄存器2执行四字节整数加法,结果在通用寄存器2
将通用寄存器2存储四个字节,到栈顶+RESULT的位置
引用很大程度上只是语法糖,实际编译出来的实现可能是:
什么都不做,只是编译限制。比如同作用域里的别名:
int a = 1;
int& b = a;
就是个地址,比如作为成员、作为函数参数:
struct van
{
int& fuckyou;
}
void deep_dark_fantasy(int& ass_we_can);
对于C++,忽略RTTI和try catch的事情,C++和C没有实质上的区别:
类型(在运行时)并不存在,只是编译器、语言标准给你的幻境。
类基本上就是结构体;
对象方法只不过是把对象实例作为隐藏参数的函数;
虚函数只不过是虚表、函数指针;
operator只不过是名字有点特别的函数;
模板实质上是代码生成的过程。
那么这里就没有什么神奇的地方了。
对于编译过程,首先C++的编译速度是臭名昭著的慢,快的只是编译出来的程序运行快。至于为什么编译出来的东西快,原因是多方面的:
C++本来就是用于开发性能敏感项目的,人家在写程序的时候就会注意性能问题。
C++通常用于编译到native code,直接由CPU执行,那么相比隔了一层解析器的语言通常会更快。
C++这种编译与运行时分离的语言,可以在编译时使用更耗时间的优化技术,相比运行时才编译的脚本语言会快。
至于编译器怎样优化,你学了编译原理就知道了(我并没有学过)。大致上来讲,现在的代码是给人看的,会有很多对于机器逻辑是冗余的部分,编译器会把这些冗余逻辑“收”起来。你可以看看GCC文档的优化选项部分(
Optimize Options
gcc.gnu.org
),从中了解一个完备的现代编译器有哪些优化内容。
如果你是面试官,我来应聘一年java后台开发经验,你会问什么
很久没有面试过一年Java开发经验的小伙伴了,那如果你是一年开发经验,并且已经通过了前面的初筛,也就是已经约好面试时间了。
现在开始咱们的面试。
面试官:您先自我介绍一下吧~
候选人:我毕业于xx大学计算机科学与技术专业,工作经验一年,工作期间主要负责xx项目的xx功能开发。熟悉的技术栈有Java基础,集合,多线程等知识,熟练使用SpringBoot框架。
PS:废话不要多说,说你真实的开发项目经验和你真正熟悉的技能栈。
面试官:ok,好的,那咱们先聊点Java基础吧,HashSet在项目中用过吗?它的底层实现你了解吗?
候选人:HashSet底层是HashMap,HashSet里的每一个元素就是HashMap里面的key。
面试官:那比如现在我要装一个User对象进去,比如Set《User》 users = new HashSet《》(10);那么对于这个User对象有没有什么要求?
候选人:需要重写hashCode和equals方法。
面试官:ok,为什么要重写hashCode和equals方法呢?
候选人:首先,HashSet的特性是不可重复的并且是无序的。因为它的底层是HashMap实现的,HashMap的key就是HashSet的元素,所以他无法保证有序。那不可重复的就是当我们put一个KV数据到HashMap中去的时候,首先需要计算这个key的hashcode,然后在和当前hashMap的容量-1作与元算,即hashcode(key) & (n-1)来计算出他所在hashMap数组中的下标位置,这就是为什么要实现它的hashcode方法。那为什么还要实现equals方法呢?因为hashcode方法存在哈希冲突,无法决定一个对象的唯一性。
ps:能说到这你就成功了一半了!但是还没结束,因为面试官想知道你到底了解多少!
接下来会是什么问题呢?
- 那我们聊聊HashMap吧~
- HashMap的实现原理、扩容机制、为什么初始容量是16?为什么扩容都是2的n次方?
- HashMap是线程不安全的,线程安全的有哪些?HashTable、Collections.synchronizedMap、ConcurrentHashMap
- HashTable、Collections.synchronizedMap、ConcurrentHashMap都是怎么保证线程安全的?
- Volatile关键字的实现原理、synchronized关键字的实现原理
- 锁优化、锁升级
- 对象头mark word
- 用户态、内核态
先就这些吧,上面你能聊到哪就聊到哪,都是Java基础,不会了咱就停止,换一个方向~
后面会在问你数据库方面的知识,比如Innodb存储引擎的实现原理,B+树,为什么不用B树、二叉树等等。
什么是聚集索引、什么是非聚集索引、覆盖索引?回表是什么意思?
还有很多数据库的知识,有一句话叫做:“你会的越多,你不会的越多!”
如果上面你都ok,不需要问你项目经验,来干活吧!毕竟你只有一年工作经验,没啥项目可问的,又不是你主导的。
实在上面答的不好,只能问问你项目中的开发思路怎么样了,会尝试找你开发功能的漏洞,看你怎么临场发挥,说说你的思路,看看你的逻辑思维是否严谨。
以上,看看是否你都会,如果你都会,抓紧学习更多的知识去!如果你不会,抓紧学习更多的知识去!学无止尽~
上面的那些,由浅入深,五年十年的都可以问,有人说工作很多年的没必要问这些基础,我不这么认为。如果你开发多年,这些基础我认为你应该是深入骨髓的,无须准备便可脱口而出的,其他任何理由都是借口。
Java学到什么程度才能叫精通
精通:透彻理解并能熟练掌握
看了精通的意思,可能很多人都不敢说自己真的精通Java!原因有2点:
- 精通这个词是不能乱用的,因为行业里总有你不会的。想想在自己的工作中,你没有问过他人Java相关问题吗?我相信工作中肯定都问过!
- 学无止境,何来精通?Java作为一门编程语言,它也在不断的变化,比如说从Java9-Java10,这不都是在不断的变化吗?
学习并不是一蹴而就的,在工作中我们肯定会遇到问题,这就需要我们去认真的学习,毕竟不学就不会,升职加薪神马的都说枉然!我们随意看看Java开发工程师的任职要求,就会发现要求会的还是蛮多的!
来看看这两个招聘信息,其实就涵盖了大部分Java开发工程师需要掌握的技能。
- Java开源框架:spring、springmvc、mybatis、hibernate等等
- 熟悉常见的数据库,并且有基本的应用能力,比如说:MySQL、oracle等
- 熟悉Eclipse、Tomcat、JDK、SVN运行环境的配置;
- 熟悉Maven的使用,理解Maven的原理与使用技巧
如何自学Java?
有多少小伙伴想自学Java或者是正在学Java的路上?你有一整套的学习方法吗?如果没有,这个肯定能帮到你!
分享Java学习线路图这个学习线路图把每个知识点都涵盖进去了,可以查看大图!下面说分阶段的,配合视频学习!
第一阶段:
第二阶段
第三阶段:
第四阶段
第五阶段
希望能帮助到你呦。
更多文章:
excel公式vlookup函数用法(excel表格vlookup函数使用步骤)
2023年8月30日 06:20
requirement翻译(entry requirement是什么意思)
2024年6月29日 14:40
strcpy如果后一个比前一个长(strcpy(t,b)这个是把b复制到t中,t原有的内容消失,如果t的长度比b要大,t中是只消失被)
2024年7月16日 15:53
什么软件可以无缝切图(把4张图p在一起的无缝p图是什么软件)
2024年7月9日 10:45
error network error什么意思(network error是什么意思)
2024年7月15日 19:48
replacement的反义词(假肢 [jiǎ zhī]什么意思近义词和反义词是什么英文翻译是什么)
2024年7月16日 07:15
std::thread(为什么c++开发中很少使用std:sthread)
2024年7月30日 18:45
servletcontext获取(ServletContext对象)
2024年8月5日 21:55